From cc70e4aa11835ed14093d41680244b8724b0c813 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Sat, 8 Jun 2013 00:23:50 +0200 Subject: [PATCH 01/40] Bug 880752 - Console.jsm stdout/err output is joined with commas and nukes newlines; r=jwalker --- toolkit/devtools/Console.jsm | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/toolkit/devtools/Console.jsm b/toolkit/devtools/Console.jsm index 5126e45b0f57..53abd3d25503 100644 --- a/toolkit/devtools/Console.jsm +++ b/toolkit/devtools/Console.jsm @@ -111,11 +111,12 @@ function getCtorName(aObj) { * * @param {any} aThing * The object to be stringified + * @param {boolean} aAllowNewLines * @return {string} * A single line representation of aThing, which will generally be at * most 80 chars long */ -function stringify(aThing) { +function stringify(aThing, aAllowNewLines) { if (aThing === undefined) { return "undefined"; } @@ -145,7 +146,10 @@ function stringify(aThing) { return aThing.toString().replace(/\s+/g, " "); } - let str = aThing.toString().replace(/\n/g, "|"); + let str = aThing.toString(); + if (!aAllowNewLines) { + str = str.replace(/\n/g, "|"); + } return str; } @@ -465,9 +469,9 @@ function createDumper(aLevel) { let frame = getStack(Components.stack.caller, 1)[0]; sendConsoleAPIMessage(aLevel, frame, args); let data = args.map(function(arg) { - return stringify(arg); + return stringify(arg, true); }); - dumpMessage(this, aLevel, data.join(", ")); + dumpMessage(this, aLevel, data.join(" ")); }; } From 483cd2a2e04127d6ba1de02cd33183d975b0a07f Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Thu, 8 Aug 2013 09:35:55 +0200 Subject: [PATCH 02/40] Bug 880752 - Fix jetpack tests to account for new stringified output, r=test-only --- addon-sdk/source/test/test-plain-text-console.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addon-sdk/source/test/test-plain-text-console.js b/addon-sdk/source/test/test-plain-text-console.js index a0ecea6cfee4..f757f8343e42 100644 --- a/addon-sdk/source/test/test-plain-text-console.js +++ b/addon-sdk/source/test/test-plain-text-console.js @@ -36,15 +36,15 @@ exports.testPlainTextConsole = function(test) { test.pass("PlainTextConsole instantiates"); con.log('testing', 1, [2, 3, 4]); - test.assertEqual(lastPrint(), "console.log: " + name + ": testing, 1, Array [2,3,4]\n", + test.assertEqual(lastPrint(), "console.log: " + name + ": testing 1 Array [2,3,4]\n", "PlainTextConsole.log() must work."); con.info('testing', 1, [2, 3, 4]); - test.assertEqual(lastPrint(), "console.info: " + name + ": testing, 1, Array [2,3,4]\n", + test.assertEqual(lastPrint(), "console.info: " + name + ": testing 1 Array [2,3,4]\n", "PlainTextConsole.info() must work."); con.warn('testing', 1, [2, 3, 4]); - test.assertEqual(lastPrint(), "console.warn: " + name + ": testing, 1, Array [2,3,4]\n", + test.assertEqual(lastPrint(), "console.warn: " + name + ": testing 1 Array [2,3,4]\n", "PlainTextConsole.warn() must work."); con.error('testing', 1, [2, 3, 4]); @@ -64,20 +64,20 @@ exports.testPlainTextConsole = function(test) { prints = []; con.log('testing', undefined); - test.assertEqual(lastPrint(), "console.log: " + name + ": testing, undefined\n", + test.assertEqual(lastPrint(), "console.log: " + name + ": testing undefined\n", "PlainTextConsole.log() must stringify undefined."); con.log('testing', null); - test.assertEqual(lastPrint(), "console.log: " + name + ": testing, null\n", + test.assertEqual(lastPrint(), "console.log: " + name + ": testing null\n", "PlainTextConsole.log() must stringify null."); // TODO: Fix console.jsm to detect custom toString. con.log("testing", { toString: function() "obj.toString()" }); - test.assertEqual(lastPrint(), "console.log: " + name + ": testing, {}\n", + test.assertEqual(lastPrint(), "console.log: " + name + ": testing {}\n", "PlainTextConsole.log() doesn't printify custom toString."); con.log("testing", { toString: function() { throw "fail!"; } }); - test.assertEqual(lastPrint(), "console.log: " + name + ": testing, {}\n", + test.assertEqual(lastPrint(), "console.log: " + name + ": testing {}\n", "PlainTextConsole.log() must stringify custom bad toString."); From ac0a131eacc81d084a1ade3af52613ce8f142f3c Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 8 Aug 2013 08:06:02 -0700 Subject: [PATCH 03/40] Bug 899695 - Fix exception in "mach python" on Windows [r=gps] DONTBUILD (not part of the build) --- mach | 3 +-- python/mach_commands.py | 6 ++++-- testing/mochitest/mach_commands.py | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mach b/mach index 9aba2825387e..dbbd8fca40e2 100755 --- a/mach +++ b/mach @@ -33,8 +33,7 @@ for dir_path in ancestors(os.getcwd()): # to look for a config file at the path in $MOZCONFIG rather than # its default locations. # - # Note: subprocess requires native strings in os.environ Python - # 2.7.2 and earlier on Windows. + # Note: subprocess requires native strings in os.environ on Windows os.environ[b"MOZCONFIG"] = str(info["mozconfig"]) if "topsrcdir" in info: diff --git a/python/mach_commands.py b/python/mach_commands.py index 50286ba232f9..3d920b0e6c1a 100644 --- a/python/mach_commands.py +++ b/python/mach_commands.py @@ -59,7 +59,8 @@ class MachCommands(MachCommandBase): return self.run_process([self.python_executable] + args, pass_thru=True, # Allow user to run Python interactively. ensure_exit_code=False, # Don't throw on non-zero exit code. - append_env={'PYTHONDONTWRITEBYTECODE': '1'}) + # Note: subprocess requires native strings in os.environ on Windows + append_env={b'PYTHONDONTWRITEBYTECODE': str('1')}) @Command('python-test', category='testing', description='Run Python unit tests.') @@ -109,7 +110,8 @@ class MachCommands(MachCommandBase): [self.python_executable, file], ensure_exit_code=False, # Don't throw on non-zero exit code. log_name='python-test', - append_env={'PYTHONDONTWRITEBYTECODE': '1'}, + # subprocess requires native strings in os.environ on Windows + append_env={b'PYTHONDONTWRITEBYTECODE': str('1')}, line_handler=_line_handler) return_code += inner_return_code diff --git a/testing/mochitest/mach_commands.py b/testing/mochitest/mach_commands.py index 5e14e278ad17..7ec1afc08b8c 100644 --- a/testing/mochitest/mach_commands.py +++ b/testing/mochitest/mach_commands.py @@ -149,7 +149,6 @@ class MochitestRunner(MozbuildObject): return 1 options.testPath = test_path - env = {'TEST_PATH': test_path} if rerun_failures: options.testManifest = failure_file_path From 232e92dbf696d8deca1b5a2b20217dce0cb62dad Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Tue, 6 Aug 2013 16:30:17 -0700 Subject: [PATCH 04/40] Bug 902059 - Enable mixed content blocking by default. r=mfinkle --- mobile/android/app/mobile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 965510a93c8c..695a0cd6bd02 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -478,10 +478,8 @@ pref("security.alternate_certificate_error_page", "certerror"); pref("security.warn_viewing_mixed", false); // Warning is disabled. See Bug 616712. -#ifdef NIGHTLY_BUILD // Block insecure active content on https pages pref("security.mixed_content.block_active_content", true); -#endif // Override some named colors to avoid inverse OS themes pref("ui.-moz-dialog", "#efebe7"); From 2b062640efb401d1606b23ae6c426cdfec844826 Mon Sep 17 00:00:00 2001 From: Shilpan Bhagat Date: Tue, 6 Aug 2013 16:38:00 -0700 Subject: [PATCH 05/40] Bug 853844 - Optimized pageload throbber. r=mfinkle --- mobile/android/base/BrowserToolbar.java | 26 +++++++++++++----- mobile/android/base/Makefile.in | 15 ++-------- .../base/resources/anim/progress_spinner.xml | 16 +++++++++++ ...ss_spinner_10.png => progress_spinner.png} | Bin .../drawable-mdpi/progress_spinner_1.png | Bin 1994 -> 0 bytes .../drawable-mdpi/progress_spinner_11.png | Bin 1992 -> 0 bytes .../drawable-mdpi/progress_spinner_12.png | Bin 2015 -> 0 bytes .../drawable-mdpi/progress_spinner_2.png | Bin 1963 -> 0 bytes .../drawable-mdpi/progress_spinner_3.png | Bin 2014 -> 0 bytes .../drawable-mdpi/progress_spinner_4.png | Bin 2002 -> 0 bytes .../drawable-mdpi/progress_spinner_5.png | Bin 1982 -> 0 bytes .../drawable-mdpi/progress_spinner_6.png | Bin 2022 -> 0 bytes .../drawable-mdpi/progress_spinner_7.png | Bin 2014 -> 0 bytes .../drawable-mdpi/progress_spinner_8.png | Bin 1993 -> 0 bytes .../drawable-mdpi/progress_spinner_9.png | Bin 2013 -> 0 bytes .../resources/drawable/progress_spinner.xml | 22 --------------- .../layout-large-v11/browser_toolbar.xml | 5 ++-- .../base/resources/layout/browser_toolbar.xml | 5 ++-- .../android/base/resources/values/dimens.xml | 2 +- 19 files changed, 44 insertions(+), 47 deletions(-) create mode 100644 mobile/android/base/resources/anim/progress_spinner.xml rename mobile/android/base/resources/drawable-mdpi/{progress_spinner_10.png => progress_spinner.png} (100%) delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_1.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_11.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_12.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_2.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_3.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_4.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_5.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_6.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_7.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_8.png delete mode 100644 mobile/android/base/resources/drawable-mdpi/progress_spinner_9.png delete mode 100644 mobile/android/base/resources/drawable/progress_spinner.xml diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index 23d97123b6c9..b9ab84e8fc98 100644 --- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -50,6 +50,7 @@ import android.view.Window; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.view.animation.AlphaAnimation; import android.view.animation.Interpolator; import android.view.animation.TranslateAnimation; @@ -89,7 +90,7 @@ public class BrowserToolbar extends GeckoRelativeLayout public ImageButton mStop; public ImageButton mSiteSecurity; public PageActionLayout mPageActionLayout; - private AnimationDrawable mProgressSpinner; + private Animation mProgressSpinner; private TabCounter mTabsCounter; private ImageView mShadow; private GeckoImageButton mMenu; @@ -103,6 +104,7 @@ public class BrowserToolbar extends GeckoRelativeLayout private boolean mShowSiteSecurity; private boolean mShowReader; + private boolean mSpinnerVisible; private boolean mAnimatingEntry; @@ -221,7 +223,7 @@ public class BrowserToolbar extends GeckoRelativeLayout mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE); mActivity.getSiteIdentityPopup().setAnchor(mSiteSecurity); - mProgressSpinner = (AnimationDrawable) res.getDrawable(R.drawable.progress_spinner); + mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner); mStop = (ImageButton) findViewById(R.id.stop); mShadow = (ImageView) findViewById(R.id.shadow); @@ -787,16 +789,26 @@ public class BrowserToolbar extends GeckoRelativeLayout // are needed by S1/S2 tests (http://mrcote.info/phonedash/#). // See discussion in Bug 804457. Bug 805124 tracks paring these down. if (visible) { - mFavicon.setImageDrawable(mProgressSpinner); - mProgressSpinner.start(); - setPageActionVisibility(true); + mFavicon.setImageResource(R.drawable.progress_spinner); + //To stop the glitch caused by mutiple start() calls. + if (!mSpinnerVisible) { + setPageActionVisibility(true); + mFavicon.setAnimation(mProgressSpinner); + mProgressSpinner.start(); + mSpinnerVisible = true; + } Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start"); } else { - mProgressSpinner.stop(); - setPageActionVisibility(false); Tab selectedTab = Tabs.getInstance().getSelectedTab(); if (selectedTab != null) setFavicon(selectedTab.getFavicon()); + + if (mSpinnerVisible) { + setPageActionVisibility(false); + mFavicon.setAnimation(null); + mProgressSpinner.cancel(); + mSpinnerVisible = false; + } Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop"); } } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index ccce5d4d012d..b54c3ad4b30b 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -597,6 +597,7 @@ RES_ANIM = \ res/anim/awesomebar_hold_still.xml \ res/anim/grow_fade_in.xml \ res/anim/grow_fade_in_center.xml \ + res/anim/progress_spinner.xml \ res/anim/shrink_fade_out.xml \ $(NULL) @@ -647,18 +648,7 @@ RES_DRAWABLE_MDPI = \ res/drawable-mdpi/ic_menu_reload.png \ res/drawable-mdpi/ic_status_logo.png \ res/drawable-mdpi/icon_pageaction.png \ - res/drawable-mdpi/progress_spinner_1.png \ - res/drawable-mdpi/progress_spinner_2.png \ - res/drawable-mdpi/progress_spinner_3.png \ - res/drawable-mdpi/progress_spinner_4.png \ - res/drawable-mdpi/progress_spinner_5.png \ - res/drawable-mdpi/progress_spinner_6.png \ - res/drawable-mdpi/progress_spinner_7.png \ - res/drawable-mdpi/progress_spinner_8.png \ - res/drawable-mdpi/progress_spinner_9.png \ - res/drawable-mdpi/progress_spinner_10.png \ - res/drawable-mdpi/progress_spinner_11.png \ - res/drawable-mdpi/progress_spinner_12.png \ + res/drawable-mdpi/progress_spinner.png \ res/drawable-mdpi/tab_indicator_divider.9.png \ res/drawable-mdpi/tab_indicator_selected.9.png \ res/drawable-mdpi/tab_indicator_selected_focused.9.png \ @@ -1118,7 +1108,6 @@ RES_DRAWABLE += \ res/drawable/ic_menu_quit.xml \ res/drawable/menu_item_state.xml \ res/drawable/menu_level.xml \ - res/drawable/progress_spinner.xml \ res/drawable/remote_tabs_child_divider.xml \ res/drawable/shaped_button.xml \ res/drawable/site_security_level.xml \ diff --git a/mobile/android/base/resources/anim/progress_spinner.xml b/mobile/android/base/resources/anim/progress_spinner.xml new file mode 100644 index 000000000000..f3eb6e01bcee --- /dev/null +++ b/mobile/android/base/resources/anim/progress_spinner.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_10.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner.png similarity index 100% rename from mobile/android/base/resources/drawable-mdpi/progress_spinner_10.png rename to mobile/android/base/resources/drawable-mdpi/progress_spinner.png diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_1.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_1.png deleted file mode 100644 index 6b543a96b659294219ebdc552a41f4ff27129028..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1994 zcmV;*2Q~PKP)M;ZRAs=Md8v$GFB z_UUYFBS)5%hZ5}ANkBx%2go532S6MWMam@-Igm34kl+GB3gQywl5pTa1d>G}AtVb7 z5wfCKiAabIiHWn0AG@3N?yRS$=h4-CRn}G>Ap;4a6c3k(V~Jizp&LoGLy6Aq4wBAapzuqqZ(QmF8Kb1Yi)Y3o zoLeX$)-OPnzKL=@K(QvFC|G$!B^7r%;n~o9jx~YwB)TnuXj`IrD}fH*ATvM2=4LdG zVFw^jli>Mt^9M-pk5HLeMtMe}P@=|iAPn}Z(vi)p6o?y!m3Ao5+>i)2K83HJ$J+Ww zR!~l1nP=MxUpNX;_ywjG7f_!|P^oBmGU5AdjP;WIDfUk(iQ$?LbCP(JwyA@H+rT^Tnki67OZy=ND0(65M744`}Tr3Y`|v3ExND{wv?T%M~|x z_35OG1oj}AI}K4j0l!2*Ql+MHCX2bc`Gr8^$`$y%ui)ld(}3{$)NAn^Cim<|wQh73 z>E_tN>~w)>%LslMYa5r*YlB`7YL=3xwjliv=%l~K^z;$Z{bQ)Qeh*6JY^g)t z=Aq#iuj9tG*AQ;=nXJWRLz9?%k}>xQ*@)wm8LsK{&Y@tGC_GMf2Br{c7`M0fp%wnl z!_smU#p(knRpZp|Msvn5`H8mP!_DizL37(_sP(YFCnIrffe=Ku<;4VW`HjATQS;~!YqoK$N|9?LNH0GW*_a?2E5H+-Q zDT z2Q%=zjuo6OaE^8nx|daqk#wC7otxL=@*^M28AK|d9 zD`p}Ro7SO56K(!yQcdd){$S1!$*QfCddPXHtf-pQsZ;6)en1zZNBjLy8%MoN0Zenv zY&sH*8T@zZSQ~!pkBC~C6qan7B#7VgjGJ140t0o0U?A!`=XQ2wVAKc9{hk!?x)^oN z0v=j<4ewoLO{jj=&Um-a;^Bj@G8OA}E~@kan7uX|iY4arRUqE2=8g9ai~_OuH56nA z$CqE>{VVR}wE4%TA?|%nI}OZWBzi%Qj(Lndz<9yHU9@p25C>-eE<1MUnejhZUXz?3 zXhw!+6Y?dj9GbO-Y8?G{$^+TL$G=fVH(uIh*E&5@zr*|;ne@~51y97xj*JBLv?=#v zh2JR~+IGk6701#nJIar-?V7@aPj^wT@1w@^P|`)TZ6|z_%%@PU^sv48=}<)M21JYq zKXv*rl`tK&vTyZJ2eyi{XRe#>#>DEM5Jg=pyzd8OpLw{Wj2ShN#sh*lsUc790q+D~e16d=|@?tS@1D&o$iI z2zL)n3V!5?8WvVgGQ}UFBM-{E$idgiCpdrhs!`svx!$x|iT(qk=DqN0Gf={1Y{N*A z4yo8B1;GI}C(q%&`!CZRmk4T=V0Ym8rZ=om*eqN2C1%zIt|fTi5U%>lxJ+w|yTvB= zL)$x8G(R}wC^OB-q5JltH1Ry`Ub3B_$dzYpid0v#I%-~H)(SJ<<6ia=lAW_3v#8N@ zUSj96oR4y1ca6*%`mvUldy2ISZ@ir6QpYg%@ZTcIZdwpyJ1~L#mzHBr?n&bi} z3S?s++bLm6TAxypD>tJbD%rC&9M$ahE;7&j4IjP};da_n=7wZ;-43G5VEGVBd2+i3`NfW0>~#4 zwhAbf*tpO^msW@fN6E%L1hj|On*I2&w6mfnxzA-C@-5dx8<>a?RM@ c;=ck602nY|8m>T2D*ylh07*qoM6N<$g4p5D%K!iX diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_11.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_11.png deleted file mode 100644 index 7f3d12ddeb9224f2bb2f672c725d42e9d4ba59b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1992 zcmV;(2RHbMP)%;Sbw$AjISO z`4Xb`j}hmeLUA%czM^Ot@H{T?EY~XcPU1vC?j(jF2qum~w6-2ced8ya_I_w z`La1+eOAyPaGH8&XHO$aUP1B5Q<#20V`9Og$Y|$tmH;Tu+4fEh?0k za#*|fZ9aJf-+Zh%Y+MdKBIFMu$-jt+>wy>B;Evnk7ppp z2aWFs;5=u(ZTK7}m&zEMp#oo2C7D?FXPD5oq_{~T_nLK}70EsF!1m#}w_;#&>}qz0 zq`|`kQP|G`m3clpS4I-Nz;KVDG?_pvQIj&K`elz+R5>5WevEpK$qN(kG#KpWr;kk4~XH71Ow%(JS?+ z(;~aK{tDfG2DQx_{LQNCa7EG$EO;LvI*bzB~XR6;0e;kX}FW z=mWeslhLzloN2LMR+hiRl}mL^BwUhv8kLz2a=9d9bXr`UCS~(^+*!YX2Ayf{fVIvi z;=M_0R+P!L+>>XmJ<2Zdt4mcbxb6(ciBD}BQa_Sft$CfC=9xEVX3Gf29wgg4Zb_GL zhVHd$uj9_f1w`Aaa%=kCN{!&YfRV_^x*5tJU#X&A-+`x%)P?&~?u!4_aeVr7K5uzn zMR8n{@iybGSc&OH3%24?q4vckNpk01Wxaq0avt7%xZ1Zhin13-u{qT$A z@&>umSQ&I8SXDE7t`Ya$4rD}gYv`* z78bverVk$sNUzTM&Y4dhS`(`wUM*XsMN#Bi4tM9yvW5FeKieE-9 zNa-ZO3T1ni(N%vPi{1%n`vPwL@4fqu*}|7oXQoKJFQQmpMu9^~;3?;n&CJJ13EQQTehhot=dtwRYqa!FHl6Z4UeUp6OakFUse#WJ?!r-3ECu`LT%$BZoOS~11+6T zhf)^>?tHgzu3=&6r-c19O8gM<(e6#o9#s?=jWA@rjHg4YYuD0NxWwa5<9RB}v_f}~ zSB}!0FPbrS?)?_4x2tZf%jSzdvt+E25qtIaRW$CM$Ie}P+6i6gNL6~P+yFgeX!)9|&K-@rM>-!9sj8r8O zC!ngfs2fr3{SDR*cy74)w79J|cK<<+{Q@`Me2=-$23mWcZ^JH#hzr+l?Q%|hhLKr8 zl>Zi4eS`#K-FPjAT=l;rv(epswqygU>BKDWYrK9HH>)+L&k=oKrcB=*27vyQDexsC diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_12.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_12.png deleted file mode 100644 index c7b271592af6f20109018d00108ebe92c2578f5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2015 zcmV<52O#)~P)LoEFx6hRREBPjZ(MXl%$6&3ZTs8uQWqb2?j z6=REIw1)h8k(3t+o z#s#ogE<%e3DW{?3ampcRJ;ymdCpIZpf%q*>{Tn3yi0$1bpWkB7!Oq5jDGxHmOMBk*QCcm)9`=fLqKge$pDB91AcMAVfCTLQgSAENg>eCKIw)z4>!Owr*E>&hkw z<8if!NPQD>=2MtmkO)ekVybf;NSVwX%vV+fbS%)1DBS?zjfc^?{t!wFzd_)B71!#m zX%@KS4%+FaF1QajeZxQepTy(Y{L#KW} z7Av2__RjivTI58?mzGXJGG}BMq7PtgS*Z5!8V}i8;~{$ju4N)g+2X z*UMt8)3!Di;c@^TF#{~eT90?bwkFHejpHr~f{q1V^wb}4Nn(gk`w!Dj@qQaLfOel!I~js%G1(i?O8 zuWcZCY)=?a<`e~+c=2MNxGNk?LUnH=jcqPSF} za$}ZCH%>mi5D0I+3f=t{wstlU@jmWTRGK+?4#GPJC-@a}eH>x%3`FUE__-{;jIcny z!ZqP4x|e>)p)bH5Artz;wG{+&520A0Nz&js!!^2{2x0wa*uMM(E^ln0wF9(oFzgK) zm5TY_;aHo`HaE^=ck@BSTi->um84>T8p-41FEA}LcjJ*Wg(L;Wdo4eYVp%ZfvCUfo zIZ4lB^tOMAt*bvrdk@xgMcamvY*Q5TLQxUt^nkvJQ1oq-m5We*4Ss$I7gE6UukrqN z%4-tKhx5qI+=u+EIb13Ye9Z7FguAaK%5QRG0!_s@m^P6ZX6Bf=U9Q=we*?X1e?g~B zk|IY$eaZ1AZa?$~gE~C=8OKE6V+dwlxUR~Mma(+gh@raAptEQ4mavT&I`?`C#SX@Z zSGy+U+K+zdVZ3qm>u5&zz!5iaXX$w?73-X3V1CGj^iCqksf-b9&NRc{d>NaUFWRtc zHAGZHMjS-pIyFLb?Xzdw`0OX2<;Xv#%&bV}V-;>N3_L}p^gjGMDK*(h3@H?o^fl`J z@Ho>^?wEg94oF(bh2zgN{BgQ@qACg*1@#7jG`gc0<4`rlfU2HW{cav$06E@q^32U3 z|H;*$Sw+_}?++-H)`+=VfO7xSC42htyneF39>g$D49GYo&He0(G>4gsm~My}>+|C1 z(dPn_H?9|_z!R1kEzfG}!(MWT5!?Ke(K*3LHCr#HC?3Xw7Lzy_z7P8at6W$a`ih?6 zV3VK7DjrmJ$6!JnPx*XO@wA9&F^PvkZ=qEs^FAqnDcNa55dBS}HOjm<;`uR+xW_gg z)1n~8#`G=PIcdwykyLRa8iP)1Pm(q-P|SmVW`G~W2nruSZRLIA^?Pbc#-lKNR8F2Q za*vf%v6IxFly9}Fn+Tjmuj8Z?lwiRn1`4yLmOqw70k=?mkP&9B<9?XpSOXH&2y28Q z)vNfj#BRNbcH@7DA~UI%5|T!KMxjtTj@sH8iv{r>M(8mk1bsSn`feV*6*$t#ln)AB z9KDNwzZ5YAwtD>noz`p`o&=B%k7sdSV`lygR&RS?#EAEbhVlI5y{Fh4-b*~mH0b*V zM5c0*=&0MZq_eYij+0+SzHpQ!xn+-w@hR{XN)^SFJb;EKroPn?>g z*DP@PBNSyxE^S$@`q)HRzlPUeZZhh|0N(dHrg}Yv#`T|Ip~@yy^ldaudCmp=x2V|N z9Am1!g7@FC&JEVdpc~9#Lb0A-WW)}$5xtE7uF(;8#4K>09PX>k0gtF}8)0{yjgIXt zTq%}CoQnWv;iG2N&BAmfLYfN8g6BGRm9ERv$%NShSjCwvG)Qq&~2HXZk-*__t}Eg z`Ie(5xXqx$C;11iMvjpQ*t+}l!;^Zb$v5*&8tJJnp|MxN&ea#N@%Gzjw=(6U1{jA- z`EBz(^WFbmLcjAp8uC}$lq#*WNK6)CJ$5c4>vexV*r@dcfBav70RY=DTK5A>9XbF2002ovPDHLkV1mDC+`9k( diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_2.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_2.png deleted file mode 100644 index 709c9fa2f21cabe78b993ef51ea888315d48f5dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1963 zcmV;c2UPfpP))6C=@9xaboZ&lX&g|7|b*$r=*_r?R-+%l5e+0gL7W*E_@QnbL zmJ9stB-bYZd4#_$@p^_270(?c@fK8kjkj+gQ7_}x)-IA{%6`K;9|S;9@yhC9DD@cN ze+KzN0d7IS@f|J+;YhwG5yuiyAkhnepzU%ch&%s)6hFo0=6^}{Urx(|zs-^9ko zWg|F*L-sd-b@|fDFQnp&;I;6J_H}@i{)iv~T&%&EO4lkF6LkCb2 zP;pKmI&=i>>(5Z;`!s?8wCk&jAH!_z7?tl(rA8h1GrZfOtN(WfHydZr*`uqwe990| zm>$Rg*hPD~d)MHXzK%-xEyUGtFoC5tgdkY9#Txh6eF97JS7?0jUugur+*dKT(1Pzp zHjR{tb{Hrse-Ss{KZDj5!$DQ^4(_R#|WFxqsx*^Q#j=e z*ViCi^yE`8*4FAJ%q}dWPzsPa%oG^S3wHjE52(C-8wk5YN~awe7{04zHa32O;MQx1 z;#4{8^2sSq=``x~5^wUKL$Tb0=VUn?o7HWHwCLAp(;^*n^JuK_rqQ=C#oV@V+)skF35;UByC>A+a~MN zD5lF{NlLM1Tk!q~Xzr6y5aSh*2_-mThL@~2B&N6U+`FX#++d6{>(jI6Dt57>BLD=Z zmtryoV)!?~1dp9Z*(Wj{NWGvUhm3le7wU=ufFTm{{FvNu1a}>+*c_QuE~YSIeQZ>y z)sC_apMp~mLu@Ead5ZW?M5Eqf-G@jp@Pfm0p~zO{J=FJtz=uETgMk2*`4ezv*q-Ge z_J(cRq<-~+#Jg8Q)~N37ao-CXh@X#1(RbkJy~ihWA^C71zVh%9EH5&lRYr3e&v3QQ zW3$=f5E<$+`744T?pK2b;ZjSXq)?psEbe*WQ>JU;;65~RoO*<#JC30~yA3}$#}>@# znQ;%PynQQzjDDy)sK9ss0F*f`-F@%pQJVd5XPsL9f;a!wnrKYJ zGv&#LOMLG>?o%HDbg^*)yU_#i#BJ0HKf{HKe?z!S@$Z}?A^92r2gtd{kuTLarZ_`( z#po#l>FW8QNB6x;Z@fcg4a&9cNLFB&DlFe+IPNls*oab&$)(AIEfRmnnU_fSksisC zbeMSiQjCuu`#m>%k{KHknyXue0pUw-$E4IFR^0n2t7etlVe>_5-mu)kv!?ZgvT|SL zy6e7<7yhV>7n8*DnchoxzV%Dn*x7ms?RL(_w4-NWbESRaI|*|9ILXt?1-tSE{>~B7 zr7eyScBqj?LXGgHR{kn}dv4G6nxU~gYZ`jyd+(jg*lzv~w{OqV_58YkrsbfY7ml${ zD;q3nR1Urg2Is8(GH>t-&Y#^GcZnQHW8FI2{DQiEZ5`X2&tbDcP(hW>%u#*2?~O^q zO{~)W+Sa;>`T4VZlSGw`P+5b8mha`;G zRC1Mi&j2h xOj~A#5ZIvn4P1FMLZ_|AdOOTwUl9KnU;vhJPz9r!i8=rP002ovPDHLkV1l77$-4jm diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_3.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_3.png deleted file mode 100644 index d5ded1e253f1e5bd8754d4138e9c35a690b7e78e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2014 zcmV<42O;>0P)t|>H-wRqEfe2RTWUAiUn+tSRf?2sJcV#7Q|oEqEI#{ z3sf}{5~9$SrgEIvo;d!Q@%+D^%Xja4GkzYAow4q`@q72(^PO|Q^PO`AUi%YEk@T*^QSac+om<02$KY|sIcvXW zsuR%iRZNs0L8%-gnCL_Kt~D%$w99EbRUqNj4t|BPK-BTj-uV`q+fQTm=ry>`uL-2H z2Z25afDQMthu{S-W3n<$08Rd;x6-xV4h-uN(9%HRV<`n(PW4IxLD@xX^I0@EpWqh0 zjLtv~35H$5aa1-xHE#Z5jxf^b_QAadIvs&dPw*c2 zYyeSYKmg@%t*XTILWv-LiR$d4wY84}Cr&zWgBMYr@rhhPWx>{VcZD_F?Su$B|3Vzz zAj3aF9B*WP%tDLDxZKyFiWlG&20W2A8s@uLk$k6&&E+>RJNrCpcU#6l3c$>D-EU*E zJdcS9LF1`mkp;U?Z#&KZBI&=4mDL);9uS2o=`0cD5|q0H$G^(OPatgm5Z;mV@I0Bt zPTId>g(U4)QNR5&M&UL#DxBEUkp^&&|nX<|PFwxNTRQX)9cNzufZ)mqR2;c`mW%fCg zr;i~BbVjy;=Nj$p_i+2xdrYa+a7<%5#!zHzMi^!@8cDnhsMo&%Uoai~bL4^XN?BG2 z#nnmBZsQmMTy=12(L+#p6n-HY=7e383wC!trSbO}fz&&l9HN#P5CbA%)DoL+Z=&j) zgPglyvtmF_f0A@5T|%{5GA18IP*e>4P)=a@-0Ox^w%?)ENW*Jy=Tr6&i8b7a_)e!` z!2Fz!Brxu2%PwUj5=Z;>dH;@ z8XqA_23)Yo38%>I@Gg)P?i`7ddJW6`m<*?uNB_12Gz*C_l9LxOJ!T9+aE-Y80s7q( zKw_i7~*l381Z!U|76^D@JGZolIRAbA-UDaC{7Mg zpy<4*YL2OAd;BxV(sve7CFOqjBJ4(!-@2T1uM52 zyULhY$SsE<>heb4)UVwV#b=H>#_NU+Ax6ADC=>L!DDX$Dzh)NP^E@^>5w1yUbAw@u zb3P;_2-DIzX=?;Vygq0<^q8v+H2q4{cPPPw2&5xs2bJ_clV~@$NDzmM^=t~7p$Nba z7Vy~FvMrs589r1GOlLK73UcC`Z~`^l0`tHWGep--|Mnqsw3s0e(+F^QKGQ^9vfFV# zyG_O*`h~GAtx|2&K0*Cnje1?+gb>KM;7}ui zf&p<3=Px~j^G{9Y+o`di?>$>U>BwIpzH|kX#}(W7v`5rg3uvlpwDmUDX_HDTaOKbJ zU>W`S69FpK*BQ2DdLP+>Vfc{tMhU}~kgO3i<`z9%=Ga2u4RnxQhLxl_eDv`P*mi%6 zsRziTKxd}Y?2jp}?YlRjx1L08xx*|lEm@K_5O-I)EK2XN3x0-;NiS=_kqQ%kxh@+x z*U0n&74kSE5i^%rv=UZS6%HfN2+yOygOd}8yyjVEbvK((t;<+jqw&zHWZDxN88DUa z?w!vN_^em67iqB17hP*SvAorpM038>Lv55Q+Oq|ozfBF1SzD88OQP8UZSsEOyI5QK zkFAhdm6dynMw^Z5@)w_@*|>@*ZkWIi3rPN4Aon!hdr9%|{9h36?2uc`bq!ku+v`h^ z^`}u=xsI@vI-C(!;TxBC>8rEyxl`|;GV=v>fF^keO^K(+9!~VIiAz5vzsgZxXh|up;0fi0OJ1rXgoK3P0r3Drs81jsctzrYzVJ|_0;-5a zDGe>8LWQIea+}gvF}7pZkuULHXJ^jgJ7+H0UGEy!`n|I==bZ2VzyCTOzVllQ{Fv?^ z0=VZ$332o=@1KB>^DttH4@&$kVRT5@qO8D}ci{VPV(CV`=MXa(Lkv|Q13q$enio$( zdMDu*iWC9mOa4|=MDdwK97{xzLKI50n-Z-?#Ouot<|V8wFH@^!Y)s6`b5P^?JQwx2?6^CS))dI2|A zZ}hVvGp6r=KQK?4pU1>>6_rVYQi--n?Vv;s-EWIXMY>h?&y#}63yjZ7jL*$O7Jot| zPB@G`3S{8-AD<@GU%~iP3FV4GpfcbD%>KocC<5A{L9+!k+X5}J8}goZs}h&MaU$V) z0+p)3#9Rfk@MF5<-fktl+<;>b`lR_BDiZ~alr^_X2WXGADrh6nZpR4Q*BD^$kjnS@ zY>S6^i1jp|AB7A)0k3!x0pmr8ObG@p5zFI@8B@XL%8xL2_<06r(`uV2K(=`O1j=KR z4qSEl*jg1v8m;=j8C)-8b#;y3Kqmx&Qyx_f(qDvExPY0NyU}iZ9fh%n5cnCx${92= zE@;3!c5Z%y3jUNz+aM(PiBpq|_ERW1aFKh}Iv`|fHFp1m<^Nnjs|K`4yLExqiDaI% zi6LoEtG0x~==U+*K96|f910`Kxx%t2EfZCyYU3Fk6u%)0i_&dW>f;1w^eUa&ZfVyD zJ24u!|A3|I7jSzMsNE(TR3PqfRh%rfQEjiIPV%hIHMLE{i2o%Tn=jIBHbrF`Z_2SU zG7-SY^Gs*ny#F|hOYuIYns(VNU{8(_^VC-T8kQC>GM{-kU8dt9C9Nwd?3xzW*12cx z=8LFrUPebNS0}OnEipZV%BOH}u3~R|6a`i{U&bl8cG78gXz44?BI|RbdkW;hvYjQV zU2FF=xHoS8j77<~qLnG2JV`f|h;r7Z$q{(I=_)BH{q6U0{k^RoaM1@Y`jqGdH!ax+!1)cZMZrusAZgBB`u#hQX~(9spLp>l95uA%v}Y&c3e z+7tgL5J|n>bpa*J&_?M#O@Vzsu{(B+S)%fqn$-s37i=9!dRaGlc9~+kg^Bj-)J{To z)L^%Wlm#6JEIP7J0gGiT2U>aL=L$$0cx7I-2Ui^U~7ZwaeHd z!AVA=HwJ7>C~wj!BK8Tu4Y4=yOn*jLWALCGU`#$kD59`7#OqWbzSYG*l7dR{v6^Qa z#!v?K8{@i_utsQkmW@jG)PgADBo!{&d2Z1`XZ_w4P7WWQe;5xvGMcx}e(()3xCZpk zk~1?OfhwP2mrP1>ihzh|iTDlcf)CtKsk>OP1o;7*wf`~KEB=?|! znOp&mZHWh}(`0Fuo$o@bdjiAT>K+oQyOF=5wERDS?D@m-W=Sa4KkebVAv zyEHNSx$U4f5P&Ox+oe_usT)k*l5I@ba^U$VaORoQIP>f%Hsn6mQ=_Tsag0@ehhX}f zIBt@sLzCspesqKM$_{_fT#3V4qhVlf?J-gy6 z8?fo>#Os&F5$SvA#z)xWpGGwDD9Y0UBOGXhVz+`cPxGB&>pFD!L)YHfG# zuMtjt@*?Sbk~3V84r54_NUl9Mnjdr$#vb1I`+1hJ&oRn1d`{1WiWLHnWRtW=2UO#} z8`~TbYR}=r)p|127&kN~<bq3-1lHH~;ZvnCjD*JjP%<3XI(g zjLr)7d4`%4DR>ekF$dcE)(-UcSFygj;5uY_l+4eyU8}*@xvQ608RORoz1;<>_xL<4 zeHNpK`F1!C6iR#%36hylq_xsb(mcNtDl~8Lg=FiCwEPm<^<-)?rtiJL;@G*aRdzo0@^j(ntP6udqUYNQRl)-uW2m2 zeU-=lk#ZyL6L&r(bAf{f@>dq?EIX!$d{Jp`hiI<y~40HXzg2+(<6kpKVy07*qoM6N<$f@a>u(EtDd diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_5.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_5.png deleted file mode 100644 index f2283efbf97760b5245004800e8b71ead26090de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1982 zcmV;v2SNCWP)sEkPwjk2l+)o^dHEeB4$*Apdgcx88JU7 z$;4!m$xJd!FX`%~_P5;Yckg>u^=j#fzUFkjy6@e4zH`oZmJ)dWHP}mI{AJdK0PZ;M z^Zo?ySGll4ndOrSt|@-Dpwb%RxQZmYf;7E=&GiPf9zAR5a(j1T+zVVheqxpfJq*t~ z3D@^24jflNx(hr5j|azD zgK+OdQ1al15{?HrQuBEquIha{P)gEzjfp5ivvDVCyFcY&@AB1G@X1zVoC_R0_~ek$ zJbU%?^9aIkA*dY3#FT@u0{EVwa$0W8CRAbJN-NnnX@N<{p_e>@RtU>6JbwjAx`r_L z4;qb~PXWL)4}WGAlhfazlu@p@nPSmZCJG{lMA-APdxt<>xSj)_Ks-NSI37YNcpvrp zRuO<4P`CT@Mp8 z4rW(;1QS2T;Umjdm(GId_S`Jx=Q!o3n#pr?sutLa#!XN6Sndc0V)r>H=o1dqc7m&t5KhX27vEBi}|G*V;^x# znA<9?@`$E&n(ve4f9A$2B3hlq;DNb*xlV$v!wufR^6~;Yt?we7_yW(&!lQGZ7!XNx zJN7od!#%%(Hq3qKDd5oJome`2KV4siD~hoOUCS)5pT*V7uVdpz3-uaZ-h>5Vg@(j& z|9fq}fmZVzJfmjj@$wYrRUyI{G_bh1t9VO<=sp1OOhCQo8HJg&=u3n9Dm-WqOv8PENnGoa@>Yh zeh}fhAX);qyllp#8#G4dRkG_r(@`h7DW{-@$)!hQKkWW3k$J%_tcfuLg;LBb?d4Uq z|A2okivxqi$~VAnTZ>BkR~vt5u3Yrs4|J80_MWOmd#El@)~eSnvsf=gFH1uqQWA5{ zlJg+t!vs`gGD7DlG`_?_(@#SqsrgKH4gIS<_IE`JX05x#d_7pXIADCk5E+Ff{nto1 zIY7eHbkF#jaok288`bN6rV>hL315A*JfLaoQwTSl1!nKk@-aA-ufPo*bE+*!=0U=& zNRl@VlAEUL4}d`aYMR_@!!WG;f3}``6TF0Q+NW2YUL7#*@360^TK zw;Z!f2Qzr&)UgAV-_jveerW!O2xlK)+3mMwYklIPQ~w7C*1ELa%M6Y>?*TgZf}u%v z-Hpe;eh2=qi+bkfe*`)61C-~P?=q_aS#SlKb*5zeGozdHz!N}qBgV?@#-7>UFjcg3 zqk0$ofZ-4gG~3k?4~W7*ySTW@(d*|3=Dvxk6~Sg_$JJg^#*u^O#vibC^Ht6TEFq?k zw_D~h$7d{1rUumi<@__Oc8|dc2+WH)vi+62&Jg-p=JB;`JQcUmU-v=FN0EdNqcRVa zXE_xIvNyOJ7zt~0tGWplz0BVaw#*B>_3-z zp2gAqpha8TM~%j`BPox9r%uq@sLV}7s%EENIcY}Sl&QJ5Bt zjyM)*ZC{7leUdZfo9Hz1$*R~;hYQTRyav-CiQ>mefQUW#>p6U3=N zyT;!R>%WC)oWhN3?`D%xg5qyQ2X{R+K9eqfpz+Dx6-<`?h_tiJ5FLRQF0vmV>&UDETg7Z Q-~a#s07*qoM6N<$g5gWY%K!iX diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_6.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_6.png deleted file mode 100644 index 413fe939e5f5ce7f7907820bfb586865defb8713..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2022 zcmVx%ZxO@#^cCIJ7u& zga0o!3dk2%5#$#U_!Z8XflA6y+J{yl64gSIJb+T`khqDpJ0TYuSrb$2n3bmi7=REQ z^VO%3FI~dyTnV{C1jkd5(g&mmAth%@8g-CF5>ezJ>`RV><0c&Wd#v4VT1$G;xH;VE z*17INtq4E(GHQ)73Z)+W+yM;+c1Vbc#dJ>48v;=R#F2+y=NWYOKZY|;{SA(D6>GOb zXg%SLNdSVz8_O%0t3HQv#pSLaLOMQ;1}QT8x)?5^Ef`@>hc1(@gy#wbf#ACi+WXI< z(>jY&XMTc>_3a^u!>t}Me)_2uG*&KSp&p`8?!a{dI6S$(5wk}Kk_|n)Lm-j>@)DH= zftgwZUheByT6x-rbt(X3eDz!zjguEJGuwyfNt*St8P>`6R2-24!Wig9W(b7*4HJPR zg9*Nwt}VC$iBiQuWigLj=@rx)PmF?a?R2yJGZ!4P{F8LI$Cv!fuo~`;p+y|Wh{AOw z@p~lRHvivG!8^_@A-yUJFqlWIUL9nIC17+0mr&x3RQbL*IJ=LGeTMqR2c! z2n19pE6mq?-0yvj0KaGW;95H$zgXg=Rlb1CxE#haRtfsOYgoVi4mt$g>lsfFno7qY zd}c^4*KIeEEB=t7d=B}U&m+jmY%}DMa8RhkD7Qb#n(-u>J8O)yc~Q`~uW2^1yOtJ& zLZTJ(`5M-4zJtA8;GktZ!JTvWI!R+^Sgma8rO(=&W9DvdyovUsH_!{EHLf#HxSosB zTn}FGB?DUG{6&{HR|rIBJT^pA_HP_*;@unnLTeB70u99(Esi_*dXl7b5BSdA&8z6{ z-KH01mL<~iE>{4GGiPyPsYnRU5(|YK8W-8_m5xcaYj)Qr=4O|}jM2y1Plrn1G2V!J zKVw)1d~@ut#sih1ao8+Y+4W}Xb~RaYrg=dh0W$<)NZWgx8%cLR;DYoC z!e3?`5IJj9Pq=cIAsDo*@n_=5KkB${r_+pyr#RMTh0u@u~)ZH&Sp0Jkapn z&RE=!x_xln!{ZN|A@K*cTaJR1HZy13Z1-YFKoh4p@t6T(%>AQBQycPTxFeI{68wQw@iYGlrVn>jI<0(}+_ z(4N?qm_o>*yGQQp5mzg{3gJ4onWeo_+me^Wy9V>#G0|&GZMu)ke@sb7ZX8;E3XU&_ z8DM^6av}~lj0Y&0aLP{J{-BCm|Gi8O%wo2D9bf$1fAGuSbRdpV0Mmes?%uTBdSe-0 z@f`KQzys-sY&rjj0dU_q1Dp3x;-Bl^M=)Q8>niNFFX7F%&aqB>Hm&=PQf6VGC9{yVnQO(WlN zg=o+rYmJwYpFI!1G^l`SQ%YhA*zRAkbFVwlglo`29saC>?+pqCr!Hxh)99lRsuCzk-<)4wbbW?%ovoknK5ay^ig>8>Z5x9x&ZYs&DYY zMR@sT+nQ4lipqw+D;hn@wQln-6q$d7qu+olud(&swH{OI1oQkn0bGL4pF_Dy^)pXz z28R{G#NFtN_M=T!#UCN;TDt--|2lP=p+$3V@G+8}uwP3eBZ9WqX@?CX zAN@y7sDrO!=k6W!+hgKJj?ZXk79YTb{5M2He}&4tC^dor*kM=<%oz; zDzOHVZj8A77G3%SJZ#?LCSaA7N+yKEB!B@3Gx&{?0%6>13nggs1I`7A;4CHhB6*QZ z)l||VCw8FwYmn5DI(ZEbA0AkuMcq_IPc(`2_`d)H0O3kn&60P)2@^~*KCb{3i8xmXAd3IT>#yV7nHz4wfrbGdh8T>84k>tr2a!Yv z;CVduBz01pS^|KgNcXgnyw7v(OL{{fE-AFbIRvePxaa=U2!j*2v=Dzl0M!225n3&u zg7U_wm!Vym7qoO>2(1YCK7~MnjYpx>EP?zA@6NCEhed8v{=_q5RQzf9 z#U1bq21@AM(L)#_x?-|5otGGH};+dA$mYb6d1P3h{Wjl2tD!@?ElD=yJfb7fESJ& zRfv+0(_KDa(=O6*Ds$VZ*d)w^DS&duo9hNd^rWMe zIDQN7zV&Y`FSfCM69`)rf{3gTNXH6~!w^_|zlG+;S;n8rz5N82DtFvqUQ2o>iFadk z^bBs)TOO`EZL^uJIzwzS*VFCEn|S;5J7}&m-U0nWt4&{C+jDJ+5vPFKlq7l;d-f(U zJL!#r>=M4fbS~EvI>9rL@+F0zykx`hQ063)x!ww2##Q|1)El_DmMS-Tc%{s*>kLdp zFhO{cvHvg%#VMB+CgX%8F{5YSDtW?_>FK&~HrVo@`Lp{d+ zpt@OL_jYNI$r z4jWVa1{;L_HFOYk!(jKG)F_X$T#BE%F!U=I1HhO8d;6{t-(v0eT`5L3+SovM!i4`P zCF&~;==DF+9jW!XlxqgXi9;Bl_-;28w=2P0NXvI-c0G^s*uyA}Y?fiy+Bri+tut6& zYTEwHhvOfk(~Ml29i@P3x@$O_u5C zHx#0od!A_$ub@)c4OMx7B~oWD*6!FNAGDccZ+INF%2@FJS4yqMND+!0R@Rh+S*t$~>m!m(UU~qOz0op-B0Vld4>`9wC;P zY)QDVPJ?AbtT=iV3L@)udAgabLJEY97Qz1rmzNe@hcu@5yTFcEcD&voh~y)U+1GJ-@uFkD)zQ=7 zG7j`ECERx>KO!icRi>uygD89(vhoR5NZ)0Kl-9N+<4HO{x=$7c)t2>$KGqw5gNa|n zwW}AqG-qMNlGaNpy}7>DwUsy wNM3_5f57!?O|(h0j-8~VbUGT~!T$>|0EfF-QA{Q_Q~&?~07*qoM6N<$f}@e(y#N3J diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_8.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_8.png deleted file mode 100644 index db7d794f8ba500db245024d50ddfaabb1513469d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1993 zcmV;)2R8VLP))fGLj>eZ*Gr{`y8 z47MkO2P-(1oyhSAStLX_i%1}&2ni4$WrbL9@hl_9g4^f(vmVsnZU$Mim~y7XiPm0ztlvt@d56x-sor>P~HVF48+-ITy8uJ zr4PaNq`^Yb6tM$jcBt7eAw5OqHI&B3=!s_`WgT(!Yiz97t>ytNF24I>_pZUi%2v5~ zKSO!QTI*P~BEKTP6S;OH*&fIK5v&LZBtcMWpgdlNBagxJ*HGKu8uYQV!|P|Cnc(~P zC>3ilpgK;|_Nw#iF3FO^bm7BuODI=bm}S~Lu=mH9Klm_&7y;mgb4)5cM)7-&G|JLk z3JB&-Bjtxe67Ed<;+y%*6(Ri4!zA-#X7(u@Ix^K0ww@e#;hdW*4|mw5tZS83udu?z zUtwb2W8yWl?^T)K%+b0}gTE3NX(K;sP;LEu467;m1#(c|yq-}*jo zufB#Tp2XC|$2fKLZ#{lWY2^m@eS~XQ>WJDv9Mfu*SuC9`#(WE2@EH~kRS>yP(&|O7 zJ2{XnXMBQ4N(T@{Prz|55;&2acnO%EK8CgWMF?JHsS`;4x59>G*#m1jhwta&c;daM2+ zdUnp)L(?E+dY$DXKj5H7u4<`qya zL%3xLQx%SNeK(52rZfI803%VS%6G{PbCeM!hx8D}#ZlEf_Gw2PC*_akM=BOQokWca zgXM{7{-7YF-dt$%Eg`R~Y-^E-jx?s9w%!?3JotH71@r+H#siDg->m{8R#VcThWHc4 zT;*k$fsm{UxIZ$4HB|9#0Q8X0#&F@BV3R78XC6qXB8u+wCjAQm=uQb5hx#FDWBd$R za6b9@7y;# zUf2x;c2f>Z`PBClgyWv6{9L(X_qAF9OZ1Y1yJl8v{E4KKrG1eHrAmr$<}@CdeWMS= z*Lmq&`GNUoF*fmS0_Y~5`8G}hG`E+qzFcE0SVvCs4w72Z2Y?|%d5+iV*D*Vnff!}| zpz?hOoHdnoy{aLdQYP??h~2zEw~`JmJZ z_|8dG%2!ZtYz+;E$TNQb{C-SK{S4)qvzV9_Y&zYfHDONT0GsO{VeQU&Gfzm{YGY1T zs{Knu8)qQ*euL#FI#m&lF?aW~J(o_Rw()xon13h0izx_FlslzB*!^EQF7FH3r(G-d!7RGmGj7{ZdSV91K0+{q{Cj4x?B6-Q`o zzKzv8pVXJ_t((c`UP%(@o!E@`DC@@xxg4jgCh=|%U@navi%eqD;HQUoSKwlL5Q67 z1%^LJ>YfKUGch-68uj%*^UR;)ieBMiBthRlYx0dZ@a@%$1~-{9bS zoNQ*?%=kJ*MR)5UYdR#al+uhD(o4|sZ?L@lr9ICioldhEK@5916}_G?;b>&ZnKVCv zKlU2-#N&wN(`;1Vf$OtULHqVHf**UtWLFlf& buLKwXU@u~yX{Nu800000NkvXXu0mjfPpiYV diff --git a/mobile/android/base/resources/drawable-mdpi/progress_spinner_9.png b/mobile/android/base/resources/drawable-mdpi/progress_spinner_9.png deleted file mode 100644 index 617a77c05bc046c33c90af45a2181013d007b3de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2013 zcmV<32O{{1P)m$>f~L znVB{-leWafs-bBqR0JPtDT1N+AfZ~s0S9$*euA?^v_*)t_xAEMkx@y34F zychg8ki3&rETENuQkr0{^4SFfX~CHP7k~g}s})4a%RKjE@MQzXkG+b;xwQ#^7Jy7Q zWFQQ`Ya;m|4b0cp zCpa<=fXKc{`<*v3AT&=B#7D`@JRUy%XIyya_9O`DHjXMkTzvxl@J&>XEn|8~qr3oA z7WsL!jQ6iSJ;8w7$HO86B*+{P=J**@QJ#7l%g2wVL=E`r9Lj~sajD)$O1OVcRM z8gAx#!A}wuRt$_kH68<63^~wafh&e?u}<>+0J%blncAnYaCFImFk-?5wOSsrd!Y&JSJI8y-B1tq?V!oo62GbC1|F>VjA*SNWzcqL;5id zu9MMKv|G2Ovp$jEMEQuM1v<0Ys@90Rysm!_TerT0Xq!O#6ET+sfTnvA3!n?McIccf z;L`bX=ym^&Sj%CZaPANuU2zA+%9CV@4`ye}kmaY4uV`CJ2ARx5DVt}pwf+)%cc`G3 z#=+hGY{5}vCF0s0?zwdFpXm3lBN+hz$Y(0e5{(`^l+6ty`72DamtS!} z(;6r@6F(%0m75gL6|}(`DJ_U;AHpy2MrB1KL!Q_Qaotfb4s2uTUo7q(m^L1?-pLr4 z++Sf{N|Qyt*@bOV`pf}=Cq_Qwk@((kt<8*>D2?$)BPG)pFd)oM7Wvi8 zUq#Z{jRD61XMo}i7szipH{2}+C#@fSW*HRT=UDXu*8@y8KU#?+#XvtVv9aDl-1-wK z5kml0Y;PzYegw1iucin64>2(2dhR)T?9*&IC&{Qc^u2XMOzWfG1zfqzAhcZ!@edI- zy=(wUE27WZP^xLr)Ow`&)dKZD}b8TJ`=Ksgv#Qy_iU*nVewgztunjmE!G z3tne)cnV?I&t@62kS81S22Js0)V+tf`bSt@T{(ErC&I<0IstqE#X~1hsWP5I;o>T7 zM9MMUYoDd{>$Y?HuGqxv<0Aexx?88=*DH*HBnvi!e1Ti<)94f)LFe|**h2rt+VK{x zy2wCkIK6>i<>C`md>UTiB!>~cwoH+|P-e45`l(ozI;}PII&ZkyfyK^u)+m;<#*H54 zycf_3eu8o}mkn!aeaNwY2X)0m>ATkBh}(~nKC+|eE`s$~H*!qfu%u8ZkvV3OH;j|C z#k0ks(@YSxe}$`8Vpd1&&V-f)>zBsbMa+9&N7wrSDYPKOuxzq>!sqOnXY00Z(<&|5 zB5N#?e)fHP;-Ik&hB}UM#zVVF7Po(a>nm$+V9Cn-ZjRY$C*u5fYxN(9x4(w=#v1yu z8Y?X#3!{LF@;QS--U>m5204E?;$|l*r#^~H*x~5c+WIk8ub!v>(*`wsF&S+d*0NrU zgZH&dK1*?|0Of* zJ#?GtoM>5|cv0O2F!_pRh3^s$NRGars~1?+KFNotSXTM+;m>eQj-8HFD?2!D@;&e- vd&?!(mN&7v*+##UPRc6I6z?VOzXA*ZrQ%=L1PGb900000NkvXXu0mjff8op| diff --git a/mobile/android/base/resources/drawable/progress_spinner.xml b/mobile/android/base/resources/drawable/progress_spinner.xml deleted file mode 100644 index 937cbfab0fe0..000000000000 --- a/mobile/android/base/resources/drawable/progress_spinner.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml index dba52d77d562..02f2ebbbecc5 100644 --- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml +++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml @@ -75,8 +75,9 @@ android:layout_width="@dimen/browser_toolbar_favicon_size" android:layout_height="fill_parent" android:scaleType="fitCenter" - android:paddingLeft="8dip" - android:layout_marginRight="4dip" + android:layout_marginLeft="4dip" + android:paddingLeft="4dip" + android:paddingRight="4dip" android:layout_gravity="center_vertical" android:src="@drawable/favicon"/> diff --git a/mobile/android/base/resources/layout/browser_toolbar.xml b/mobile/android/base/resources/layout/browser_toolbar.xml index 50340b0e0dd1..3dd5634d1117 100644 --- a/mobile/android/base/resources/layout/browser_toolbar.xml +++ b/mobile/android/base/resources/layout/browser_toolbar.xml @@ -101,8 +101,9 @@ android:layout_width="@dimen/browser_toolbar_favicon_size" android:layout_height="fill_parent" android:scaleType="fitCenter" - android:paddingLeft="12dip" - android:layout_marginRight="4dip" + android:layout_marginLeft="8dip" + android:paddingLeft="4dip" + android:paddingRight="4dip" android:layout_gravity="center_vertical" android:src="@drawable/favicon"/> diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml index 681fe0f566e3..c19067d93a60 100644 --- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -30,7 +30,7 @@ 12dp 48dp 20dp - 29.33dip + 25.33dip 32dp 1dp From feda467b9e8c60f6dbc1edc09864eb3af704d764 Mon Sep 17 00:00:00 2001 From: Michael Boon Date: Thu, 8 Aug 2013 13:40:26 -0400 Subject: [PATCH 06/40] Bug 888982 - Fennec should use channel-specific build defines rather than MOZ_UPDATE_CHANNEL. r=mleibovic --- mobile/android/base/AppConstants.java.in | 9 +++++++++ mobile/android/base/BrowserApp.java | 3 +-- mobile/android/base/UpdateService.java | 3 +-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mobile/android/base/AppConstants.java.in b/mobile/android/base/AppConstants.java.in index 120089390ae3..fa315cd1326f 100644 --- a/mobile/android/base/AppConstants.java.in +++ b/mobile/android/base/AppConstants.java.in @@ -136,4 +136,13 @@ public class AppConstants { #else false; #endif + + // See this wiki page for more details about channel specific build defines: + // https://wiki.mozilla.org/Platform/Channel-specific_build_defines + public static final boolean RELEASE_BUILD = +#ifdef RELEASE_BUILD + true; +#else + false; +#endif } diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 1b68db52ce48..05594149b022 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -1988,8 +1988,7 @@ abstract public class BrowserApp extends GeckoApp * @return true if update UI was launched. */ protected boolean handleUpdaterLaunch() { - if ("release".equals(AppConstants.MOZ_UPDATE_CHANNEL) || - "beta".equals(AppConstants.MOZ_UPDATE_CHANNEL)) { + if (AppConstants.RELEASE_BUILD) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("market://details?id=" + getPackageName())); startActivity(intent); diff --git a/mobile/android/base/UpdateService.java b/mobile/android/base/UpdateService.java index b90a64b49eea..73936435e061 100644 --- a/mobile/android/base/UpdateService.java +++ b/mobile/android/base/UpdateService.java @@ -138,8 +138,7 @@ public class UpdateService extends IntentService { int interval; if (isRetry) { interval = INTERVAL_RETRY; - } else if (AppConstants.MOZ_UPDATE_CHANNEL.equals("nightly") || - AppConstants.MOZ_UPDATE_CHANNEL.equals("aurora")) { + } else if (!AppConstants.RELEASE_BUILD) { interval = INTERVAL_SHORT; } else { interval = INTERVAL_LONG; From f332128f59f3f8236aa057cce41d514e1d703beb Mon Sep 17 00:00:00 2001 From: Allison Naaktgeboren Date: Thu, 8 Aug 2013 11:48:01 -0700 Subject: [PATCH 07/40] Bug 894811 - First character gets dropped when typing into urlbar after opening a new tab.r=mbrubeck --- browser/metro/base/content/bindings/urlbar.xml | 14 +++++++++----- browser/metro/base/content/browser-ui.js | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/browser/metro/base/content/bindings/urlbar.xml b/browser/metro/base/content/bindings/urlbar.xml index bc60a14df681..83e54dc998d8 100644 --- a/browser/metro/base/content/bindings/urlbar.xml +++ b/browser/metro/base/content/bindings/urlbar.xml @@ -281,20 +281,22 @@ @@ -313,8 +315,9 @@ if (this.focused) this.blur(); - if (aShouldRevert) + if (aShouldRevert) { this.value = this._lastKnownGoodURL; + } ]]> @@ -445,6 +448,7 @@ diff --git a/browser/metro/base/content/browser-ui.js b/browser/metro/base/content/browser-ui.js index f3b4ee49e6cc..3bb27be9cd77 100644 --- a/browser/metro/base/content/browser-ui.js +++ b/browser/metro/base/content/browser-ui.js @@ -1075,6 +1075,7 @@ var BrowserUI = { case "cmd_newTab": this.newTab(null, null, true); this._edit.beginEditing(false); + this._edit.select(); break; case "cmd_closeTab": this.closeTab(); From dbddd8d101dfd23eeb24c249aba3e885a5d33b3e Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Thu, 8 Aug 2013 13:27:54 -0700 Subject: [PATCH 08/40] Bug 901239 - Uplift Add-on SDK to Firefox r=me --- .../source/doc/dev-guide-source/credits.md | 1 + .../doc/module-source/sdk/indexed-db.md | 185 ++++--- .../doc/module-source/sdk/places/bookmarks.md | 450 ++++++++++++++++++ .../doc/module-source/sdk/places/history.md | 110 +++++ .../source/doc/module-source/sdk/request.md | 2 +- .../doc/module-source/sdk/simple-prefs.md | 17 + addon-sdk/source/lib/sdk/content/worker.js | 2 +- .../source/lib/sdk/deprecated/api-utils.js | 76 ++- addon-sdk/source/lib/sdk/net/xhr.js | 4 +- addon-sdk/source/lib/sdk/places/events.js | 121 +++++ .../lib/sdk/places/host/host-bookmarks.js | 17 +- addon-sdk/source/lib/sdk/places/utils.js | 13 + addon-sdk/source/lib/sdk/tabs/events.js | 9 +- addon-sdk/source/lib/sdk/tabs/tab-fennec.js | 11 + addon-sdk/source/lib/sdk/url.js | 14 +- addon-sdk/source/lib/sdk/util/array.js | 18 +- .../source/lib/sdk/windows/tabs-fennec.js | 12 +- .../source/python-lib/cuddlefish/__init__.py | 6 +- .../cuddlefish/tests/test_linker.py | 7 +- .../source/python-lib/mozrunner/__init__.py | 6 +- .../python-lib/mozrunner/killableprocess.py | 3 +- .../addons/private-browsing-supported/main.js | 15 +- .../private-browsing-supported/test-tabs.js | 82 +--- .../test-window-tabs.js | 85 ++++ .../test/addons/privileged-panel/main.js | 16 +- addon-sdk/source/test/favicon-helpers.js | 46 +- addon-sdk/source/test/places-helper.js | 7 + .../source/test/tabs/test-firefox-tabs.js | 34 -- addon-sdk/source/test/test-api-utils.js | 231 ++++++--- addon-sdk/source/test/test-array.js | 134 +++--- addon-sdk/source/test/test-browser-events.js | 6 + addon-sdk/source/test/test-content-events.js | 6 +- addon-sdk/source/test/test-path.js | 5 - addon-sdk/source/test/test-places-events.js | 292 ++++++++++++ addon-sdk/source/test/test-tabs-common.js | 97 ++++ addon-sdk/source/test/test-url.js | 51 +- 36 files changed, 1783 insertions(+), 408 deletions(-) create mode 100644 addon-sdk/source/doc/module-source/sdk/places/bookmarks.md create mode 100644 addon-sdk/source/doc/module-source/sdk/places/history.md create mode 100644 addon-sdk/source/lib/sdk/places/events.js create mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js create mode 100644 addon-sdk/source/test/test-places-events.js diff --git a/addon-sdk/source/doc/dev-guide-source/credits.md b/addon-sdk/source/doc/dev-guide-source/credits.md index 30386881911b..73de5d5fb304 100644 --- a/addon-sdk/source/doc/dev-guide-source/credits.md +++ b/addon-sdk/source/doc/dev-guide-source/credits.md @@ -140,6 +140,7 @@ We'd like to thank our many Jetpack project contributors! They include: * Tim Taubert * Shane Tomlinson * Dave Townsend +* [Fraser Tweedale](https://github.com/frasertweedale) * [Matthias Tylkowski](https://github.com/tylkomat) ### V ### diff --git a/addon-sdk/source/doc/module-source/sdk/indexed-db.md b/addon-sdk/source/doc/module-source/sdk/indexed-db.md index 81390e17e275..260374593ab9 100644 --- a/addon-sdk/source/doc/module-source/sdk/indexed-db.md +++ b/addon-sdk/source/doc/module-source/sdk/indexed-db.md @@ -32,20 +32,128 @@ So you can use the `indexed-db` module to access the same API: console.log("success"); }; -This module also exports all the other objects that implement -the IndexedDB API, listed below under -[API Reference](modules/sdk/indexed-db.html#API Reference). +Most of the objects that implement the IndexedDB API, such as +[IDBTransaction](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBTransaction), +[IDBOpenDBRequest](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBOpenDBRequest), +and [IDBObjectStore](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBObjectStore), +are accessible through the indexedDB object itself. The API exposed by `indexed-db` is almost identical to the DOM IndexedDB API, so we haven't repeated its documentation here, but refer you to the [IndexedDB API documentation](https://developer.mozilla.org/en-US/docs/IndexedDB) for all the details. -The database created will be unique and private per addon, and is not linked to any website database. The module cannot be used to interact with a given website database. See [bug 778197](https://bugzilla.mozilla.org/show_bug.cgi?id=779197) and [bug 786688](https://bugzilla.mozilla.org/show_bug.cgi?id=786688). +The database created will be unique and private per add-on, and is not linked +to any website database. The module cannot be used to interact with a given +website database. See +[bug 778197](https://bugzilla.mozilla.org/show_bug.cgi?id=779197) and +[bug 786688](https://bugzilla.mozilla.org/show_bug.cgi?id=786688). -## Example of Usage +## Example -[Promise-based example using indexedDB for record storage](https://github.com/gregglind/micropilot/blob/ec65446d611a65b0646be1806359c463193d5a91/lib/micropilot.js#L80-L198). +Here's a complete add-on that adds two widgets to the browser: the widget labeled +"Add" add the title of the current tab to a database, while the widget labeled +"List" lists all the titles in the database. + +The add-on implements helper functions `open()`, `addItem()` and `getItems()` +to open the database, add a new item to the database, and get all items in the +database. + + var { indexedDB, IDBKeyRange } = require('sdk/indexed-db'); + var widgets = require("sdk/widget"); + + var database = {}; + + database.onerror = function(e) { + console.error(e.value) + } + + function open(version) { + var request = indexedDB.open("stuff", version); + + request.onupgradeneeded = function(e) { + var db = e.target.result; + e.target.transaction.onerror = database.onerror; + + if(db.objectStoreNames.contains("items")) { + db.deleteObjectStore("items"); + } + + var store = db.createObjectStore("items", + {keyPath: "time"}); + }; + + request.onsuccess = function(e) { + database.db = e.target.result; + }; + + request.onerror = database.onerror; + }; + + function addItem(name) { + var db = database.db; + var trans = db.transaction(["items"], "readwrite"); + var store = trans.objectStore("items"); + var time = new Date().getTime(); + var request = store.put({ + "name": name, + "time": time + }); + + request.onerror = database.onerror; + }; + + function getItems(callback) { + var cb = callback; + var db = database.db; + var trans = db.transaction(["items"], "readwrite"); + var store = trans.objectStore("items"); + var items = new Array(); + + trans.oncomplete = function() { + cb(items); + } + + var keyRange = IDBKeyRange.lowerBound(0); + var cursorRequest = store.openCursor(keyRange); + + cursorRequest.onsuccess = function(e) { + var result = e.target.result; + if(!!result == false) + return; + + items.push(result.value.name); + result.continue(); + }; + + cursorRequest.onerror = database.onerror; + }; + + function listItems(itemList) { + console.log(itemList); + } + + open("1"); + + widgets.Widget({ + id: "add-it", + width: 50, + label: "Add", + content: "Add", + onClick: function() { + addItem(require("sdk/tabs").activeTab.title); + } + }); + + widgets.Widget({ + id: "list-them", + width: 50, + label: "List", + content: "List", + onClick: function() { + getItems(listItems); + } + }); @property {object} @@ -61,71 +169,6 @@ Defines a range of keys. See the [IDBKeyRange documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBKeyRange). - -@property {object} - -For traversing or iterating records in a database. -See the [IDBCursor documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBCursor). - - - - -@property {object} - -Represents a database transaction. -See the [IDBTransaction documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBTransaction). - - - -@property {object} - -Represents an asynchronous request to open a database. -See the [IDBOpenDBRequest documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBOpenDBRequest). - - - -@property {object} - -Event indicating that the database version has changed. -See the [IDBVersionChangeEvent documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBVersionChangeEvent). - - - -@property {object} - -Represents a connection to a database. -See the [IDBDatabase documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBDatabase). - - - -@property {object} - -Enables you to create, open, and delete databases. -See the [IDBFactory documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBFactory). - - - -@property {object} - -Provides access to a database index. -See the [IDBIndex documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBIndex). - - - -@property {object} - -Represents an object store in a database. -See the [IDBObjectStore documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBObjectStore). - - - -@property {object} - -Provides access to the results of asynchronous requests to databases -and database objects. -See the [IDBRequest documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBRequest). - - @property {object} diff --git a/addon-sdk/source/doc/module-source/sdk/places/bookmarks.md b/addon-sdk/source/doc/module-source/sdk/places/bookmarks.md new file mode 100644 index 000000000000..a98a83b5f5c6 --- /dev/null +++ b/addon-sdk/source/doc/module-source/sdk/places/bookmarks.md @@ -0,0 +1,450 @@ + + +The `places/bookmarks` module provides functions for creating, modifying and searching bookmark items. It exports: + +* three constructors: [Bookmark](modules/sdk/places/bookmarks.html#Bookmark), [Group](modules/sdk/places/bookmarks.html#Group), and [Separator](modules/sdk/places/bookmarks.html#Separator), corresponding to the types of objects, referred to as **bookmark items**, in the Bookmarks database in Firefox +* two additional functions, [`save()`](modules/sdk/places/bookmarks.html#save(bookmarkItems%2C%20options)) to create, update, and remove bookmark items, and [`search()`](modules/sdk/places/bookmarks.html#search(queries%2C%20options)) to retrieve the bookmark items that match a particular set of criteria. + +`save()` and `search()` are both asynchronous functions: they synchronously return a [`PlacesEmitter`](modules/sdk/places/bookmarks.html#PlacesEmitter) object, which then asynchronously emits events as the operation progresses and completes. + +Each retrieved bookmark item represents only a snapshot of state at a specific time. The module does not automatically sync up a `Bookmark` instance with ongoing changes to that item in the database from the same add-on, other add-ons, or the user. + +## Examples + +### Creating a new bookmark + + let { Bookmark, save } = require("sdk/places/bookmarks"); + + // Create a new bookmark instance, unsaved + let bookmark = Bookmark({ title: "Mozilla", url: "http://mozila.org" }); + + // Attempt to save the bookmark instance to the Bookmarks database + // and store the emitter + let emitter = save(bookmark); + + // Listen for events + emitter.on("data", function (saved, inputItem) { + // on a "data" event, an item has been updated, passing in the + // latest snapshot from the server as `saved` (with properties + // such as `updated` and `id`), as well as the initial input + // item as `inputItem` + console.log(saved.title === inputItem.title); // true + console.log(saved !== inputItem); // true + console.log(inputItem === bookmark); // true + }).on("end", function (savedItems, inputItems) { + // Similar to "data" events, except "end" is an aggregate of + // all progress events, with ordered arrays as `savedItems` + // and `inputItems` + }); + +### Creating several bookmarks with a new group + + let { Bookmark, Group, save } = require("sdk/places/bookmarks"); + + let group = Group({ title: "Guitars" }); + let bookmarks = [ + Bookmark({ title: "Ran", url: "http://ranguitars.com", group: group }), + Bookmark({ title: "Ibanez", url: "http://ibanez.com", group: group }), + Bookmark({ title: "ESP", url: "http://espguitars.com", group: group }) + ]; + + // Save `bookmarks` array -- notice we don't have `group` in the array, + // although it needs to be saved since all bookmarks are children + // of `group`. This will be saved implicitly. + + save(bookmarks).on("data", function (saved, input) { + // A data event is called once for each item saved, as well + // as implicit items, like `group` + console.log(input === group || ~bookmarks.indexOf(input)); // true + }).on("end", function (saves, inputs) { + // like the previous example, the "end" event returns an + // array of all of our updated saves. Only explicitly saved + // items are returned in this array -- the `group` won't be + // present here. + console.log(saves[0].title); // "Ran" + console.log(saves[2].group.title); // "Guitars" + }); + +### Searching for bookmarks + +Bookmarks can be queried with the [`search()`](modules/sdk/places/bookmarks.html#search(queries%2C%20options)) function, which accepts a query object or an array of query objects, as well as a query options object. Query properties are AND'd together within a single query object, but are OR'd together across multiple query objects. + + let { search, UNSORTED } = require("sdk/places/bookmarks"); + + // Simple query with one object + search( + { query: "firefox" }, + { sort: "title" } + ).on(end, function (results) { + // results matching any bookmark that has "firefox" + // in its URL, title or tag, sorted by title + }); + + // Multiple queries are OR'd together + search( + [{ query: "firefox" }, { group: UNSORTED, tags: ["mozilla"] }], + { sort: "title" } + ).on("end", function (results) { + // Our first query is the same as the simple query above; + // all of those results are also returned here. Since multiple + // queries are OR'd together, we also get bookmarks that + // match the second query. The second query's properties + // are AND'd together, so results that are in the platform's unsorted + // bookmarks folder, AND are also tagged with 'mozilla', get returned + // as well in this query + }); + + +@class + +@constructor + +Creates an unsaved bookmark instance. +@param options {object} + Options for the bookmark, with the following parameters: + @prop title {string} + The title for the bookmark. Required. + @prop url {string} + The URL for the bookmark. Required. + @prop [group] {Group} + The parent group that the bookmark lives under. Defaults to the [Bookmarks.UNSORTED](modules/sdk/places/bookmarks.html#UNSORTED) group. + @prop [index] {number} + The index of the bookmark within its group. Last item within the group by default. + @prop [tags] {set} + A set of tags to be applied to the bookmark. + + + +@property {string} + The bookmark's title. + + + +@property {string} + The bookmark's URL. + + + +@property {Group} + The group instance that the bookmark lives under. + + + +@property {number} + The index of the bookmark within its group. + + + +@property {number} + A Unix timestamp indicating when the bookmark was last updated on the platform. + + + +@property {set} + A [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) of tags that the bookmark is tagged with. + + + + +@class + +@constructor + +Creates an unsaved bookmark group instance. +@param options {object} + Options for the bookmark group, with the following parameters: + @prop title {string} + The title for the group. Required. + @prop [group] {Group} + The parent group that the bookmark group lives under. Defaults to the [Bookmarks.UNSORTED](modules/sdk/places/bookmarks.html#UNSORTED) group. + @prop [index] {number} + The index of the bookmark group within its parent group. Last item within the group by default. + + + +@property {string} + The bookmark group's title. + + + +@property {Group} + The group instance that the bookmark group lives under. + + + +@property {number} + The index of the bookmark group within its group. + + + +@property {number} + A Unix timestamp indicating when the bookmark was last updated on the platform. + + + + +@class + +@constructor + +Creates an unsaved bookmark separator instance. +@param options {object} + Options for the bookmark group, with the following parameters: + @prop [group] {Group} + The parent group that the bookmark group lives under. Defaults to the [Bookmarks.UNSORTED](modules/sdk/places/bookmarks.html#UNSORTED) group. + @prop [index] {number} + The index of the bookmark group within its parent group. Last item within the group by default. + + + +@property {Group} + The group instance that the bookmark group lives under. + + + +@property {number} + The index of the bookmark group within its group. + + + +@property {number} + A Unix timestamp indicating when the bookmark was last updated on the platform. + + + + +@function + +Creating, saving, and deleting bookmarks are all done with the `save()` function. This function takes in any of: + +* a bookmark item (Bookmark, Group, Separator) +* a duck-typed object (the relative properties for a bookmark item, in addition to a `type` property of `'bookmark'`, `'group'`, or `'separator'`) +* an array of bookmark items. + +All of the items passed in are pushed to the platform and are either created, updated or deleted. + +* adding items: if passing in freshly instantiated bookmark items or a duck-typed object, the item is created on the platform. +* updating items: for an item referenced from a previous `save()` or from the result of a `search()` query, changing the properties and calling `save()` will update the item on the server. +* deleting items: to delete a bookmark item, pass in a bookmark item with a property `remove` set to `true`. + +The function returns a [`PlacesEmitter`](modules/sdk/places/bookmarks.html#PlacesEmitter) that emits a `data` event for each item as it is saved, and an `end` event when all items have been saved. + + let { Bookmark, Group } = require("sdk/places/bookmarks"); + + let myGroup = Group({ title: "My Group" }); + let myBookmark = Bookmark({ + title: "Moz", + url: "http://mozilla.com", + group: myGroup + }); + + save(myBookmark).on("data", function (item, inputItem) { + // The `data` event returns the latest snapshot from the + // host, so this is a new instance of the bookmark item, + // where `item !== myBookmark`. To match it with the input item, + // use the second argument, so `inputItem === myBookmark` + + // All explicitly saved items have data events called, as + // well as implicitly saved items. In this case, + // `myGroup` has to be saved before `myBookmark`, since + // `myBookmark` is a child of `myGroup`. `myGroup` will + // also have a `data` event called for it. + }).on("end", function (items, inputItems) { + // The `end` event returns all items that are explicitly + // saved. So `myGroup` will not be in this array, + // but `myBookmark` will be. + // `inputItems` matches the initial input as an array, + // so `inputItems[0] === myBookmark` + }); + + // Saving multiple bookmarks, as duck-types in this case + + let bookmarks = [ + { title: "mozilla", url: "http://mozilla.org", type: "bookmark" }, + { title: "firefox", url: "http://firefox.com", type: "bookmark" }, + { title: "twitter", url: "http://twitter.com", type: "bookmark" } + ]; + + save(bookmarks).on("data", function (item, inputItem) { + // Each item in `bookmarks` has its own `data` event + }).on("end", function (results, inputResults) { + // `results` is an array of items saved in the same order + // as they were passed in. + }); + +@param bookmarkItems {bookmark|group|separator|array} + A bookmark item ([Bookmark](modules/sdk/places/bookmarks.html#Bookmark), [Group](modules/sdk/places/bookmarks.html#Group), [Separator](modules/sdk/places/bookmarks.html#Separator)), or an array of bookmark items to be saved. + +@param [options] {object} + An optional options object that takes the following properties: + @prop [resolve] {function} + A resolution function that is invoked during an attempt to save + a bookmark item that is not derived from the latest state from + the platform. Invoked with two arguments, `mine` and `platform`, where + `mine` is the item that is being saved, and `platform` is the + current state of the item on the item. The object returned from + this function is what is saved on the platform. By default, all changes + on an outdated bookmark item overwrite the platform's bookmark item. + +@returns {PlacesEmitter} + Returns a [PlacesEmitter](modules/sdk/places/bookmarks.html#PlacesEmitter). + + + +@function + +A helper function that takes in a bookmark item, or an `Array` of several bookmark items, and sets each item's `remove` property to true. This does not remove the bookmark item from the database: it must be subsequently saved. + + let { search, save, remove } = require("sdk/places/bookmarks"); + + search({ tags: ["php"] }).on("end", function (results) { + // The search returns us all bookmark items that are + // tagged with `"php"`. + + // We then pass `results` into the remove function to mark + // all items to be removed, which returns the new modified `Array` + // of items, which is passed into save. + save(remove(results)).on("end", function (results) { + // items tagged with `"php"` are now removed! + }); + }) + +@param items {Bookmark|Group|Separator|array} + A bookmark item, or `Array` of bookmark items to be transformed to set their `remove` property to `true`. + +@returns {array} + An array of the transformed bookmark items. + + + +@function + +Queries can be performed on bookmark items by passing in one or more query objects, each of which is given one or more properties. + +Within each query object, the properties are AND'd together: so only objects matching all properties are retrieved. Across query objects, the results are OR'd together, meaning that if an item matches any of the query objects, it will be retrieved. + +For example, suppose we called `search()` with two query objects: + +
[{ url: "mozilla.org", tags: ["mobile"]},
+ { tags: ["firefox-os"]}]
+ +This will return: + +* all bookmark items from mozilla.org that are also tagged "mobile" +* all bookmark items that are tagged "firefox-os" + +An `options` object may be used to determine overall settings such as sort order and how many objects should be returned. + +@param queries {object|array} +An `Object` representing a query, or an `Array` of `Objects` representing queries. Each query object can take several properties, which are queried against the bookmarks database. Each property is AND'd together, meaning that bookmarks must match each property within a query object. Multiple query objects are then OR'd together. + @prop [group] {Group} + Group instance that should be owners of the returned children bookmarks. If no `group` specified, all bookmarks are under the search space. + @prop [tags] {set|array} + Bookmarks with corresponding tags. These are AND'd together. + @prop [url] {string} + A string that matches bookmarks' URL. The following patterns are accepted: + + `'*.mozilla.com'`: matches any URL with 'mozilla.com' as the host, accepting any subhost. + + `'mozilla.com'`: matches any URL with 'mozilla.com' as the host. + + `'http://mozilla.com'`: matches 'http://mozilla.com' exactly. + + `'http://mozilla.com/*'`: matches any URL that starts with 'http://mozilla.com/'. + @prop [query] {string} + A string that matches bookmarks' URL, title and tags. + +@param [options] {object} +An `Object` with options for the search query. + @prop [count] {number} + The number of bookmark items to return. If left undefined, no limit is set. + @prop [sort] {string} + A string specifying how the results should be sorted. Possible options are `'title'`, `'date'`, `'url'`, `'visitCount'`, `'dateAdded'` and `'lastModified'`. + @prop [descending] {boolean} + A boolean specifying whether the results should be in descending order. By default, results are in ascending order. + +
+ + +@class + +The `PlacesEmitter` is not exported from the module, but returned from the `save` and `search` functions. The `PlacesEmitter` inherits from [`event/target`](modules/sdk/event/target.html), and emits `data`, `error`, and `end`. + +`data` events are emitted for every individual operation (such as: each item saved, or each item found by a search query), whereas `end` events are emitted as the aggregate of an operation, passing an array of objects into the handler. + + +@event +The `data` event is emitted when a bookmark item that was passed into the `save` method has been saved to the platform. This includes implicit saves that are dependencies of the explicit items saved. For example, when creating a new bookmark group with two bookmark items as its children, and explicitly saving the two bookmark children, the unsaved parent group will also emit a `data` event. + + let { Bookmark, Group, save } = require("sdk/places/bookmarks"); + + let group = Group({ title: "my group" }); + let bookmarks = [ + Bookmark({ title: "mozilla", url: "http://mozilla.com", group: group }), + Bookmark({ title: "w3", url: "http://w3.org", group: group }) + ]; + + save(bookmarks).on("data", function (item) { + // This function will be called three times: + // once for each bookmark saved + // once for the new group specified implicitly + // as the parent of the two items + }); + +The `data` event is also called for `search` requests, with each result being passed individually into its own `data` event. + + let { search } = require("sdk/places/bookmarks"); + + search({ query: "firefox" }).on("data", function (item) { + // each bookmark item that matches the query will + // be called in this function + }); + +@argument {Bookmark|Group|Separator} + For the `save` function, this is the saved, latest snapshot of the bookmark item. For `search`, this is a snapshot of a bookmark returned from the search query. + +@argument {Bookmark|Group|Separator|object} + Only in `save` data events. The initial instance of the item that was used for the save request. + + + +@event +The `error` event is emitted whenever a bookmark item's save could not be completed. + +@argument {string} + A string indicating the error that occurred. + +@argument {Bookmark|Group|Separator|object} + Only in `save` error events. The initial instance of the item that was used for the save request. + + + +@event +The `end` event is called when all bookmark items and dependencies +have been saved, or an aggregate of all items returned from a search query. + +@argument {array} + The array is an ordered list of the input bookmark items, replaced + with their updated, latest snapshot instances (the first argument + in the `data` handler), or in the case of an error, the initial instance + of the item that was used for the save request + (the second argument in the `data` or `error` handler). + + + + +@property {group} +This is a constant, default [`Group`](modules/sdk/places/bookmarks.html#Group) on the Firefox platform, the **Bookmarks Menu**. It can be used in queries or specifying the parent of a bookmark item, but it cannot be modified. + + + +@property {group} +This is a constant, default [`Group`](modules/sdk/places/bookmarks.html#Group) on the Firefox platform, the **Bookmarks Toolbar**. It can be used in queries or specifying the parent of a bookmark item, but it cannot be modified. + + + +@property {group} +This is a constant, default [`Group`](modules/sdk/places/bookmarks.html#Group) on the Firefox platform, the **Unsorted Bookmarks** group. It can be used in queries or specifying the parent of a bookmark item, but it cannot be modified. + diff --git a/addon-sdk/source/doc/module-source/sdk/places/history.md b/addon-sdk/source/doc/module-source/sdk/places/history.md new file mode 100644 index 000000000000..fe3ed144588b --- /dev/null +++ b/addon-sdk/source/doc/module-source/sdk/places/history.md @@ -0,0 +1,110 @@ + + +The `places/history` module provides a single function, [`search()`](modules/sdk/places/history.html#search(queries%2C%20options)), for querying the user's browsing history. + +It synchronously returns a [`PlacesEmitter`](modules/sdk/places/history.html#PlacesEmitter) object which then asynchronously emits [`data`](modules/sdk/places/history.html#data) and [`end`](modules/sdk/places/history.html#end) or [`error`](modules/sdk/places/history.html#error) events that contain information about the state of the operation. + +## Example + + let { search } = require("sdk/places/history"); + + // Simple query + search( + { url: "https://developers.mozilla.org/*" }, + { sort: "visitCount" } + ).on("end", function (results) { + // results is an array of objects containing + // data about visits to any site on developers.mozilla.org + // ordered by visit count + }); + + // Complex query + // The query objects are OR'd together + // Let's say we want to retrieve all visits from before a week ago + // with the query of 'ruby', but from last week onwards, we want + // all results with 'javascript' in the URL or title. + // We'd compose the query with the following options + let lastWeek = Date.now - (1000*60*60*24*7); + search( + // First query looks for all entries before last week with 'ruby' + [{ query: "ruby", to: lastWeek }, + // Second query searches all entries after last week with 'javascript' + { query: "javascript", from: lastWeek }], + // We want to order chronologically by visit date + { sort: "date" } + ).on("end", function (results) { + // results is an array of objects containing visit data, + // sorted by visit date, with all entries from more than a week ago + // that contain 'ruby', *in addition to* entries from this last week + // that contain 'javascript' + }); + + +@function + +Queries can be performed on history entries by passing in one or more query options. Each query option can take several properties, which are **AND**'d together to make one complete query. For additional queries within the query, passing more query options in will **OR** the total results. An `options` object may be specified to determine overall settings, like sorting and how many objects should be returned. + +@param queries {object|array} +An `Object` representing a query, or an `Array` of `Objects` representing queries. Each query object can take several properties, which are queried against the history database. Each property is **AND**'d together, meaning that bookmarks must match each property within a query object. Multiple query objects are then **OR**'d together. + @prop [url] {string} + A string that matches bookmarks' URL. The following patterns are accepted: + + `'*.mozilla.com'`: matches any URL with 'mozilla.com' as the host, accepting any subhost. + + `'mozilla.com'`: matches any URL with 'mozilla.com' as the host. + + `'http://mozilla.com'`: matches 'http://mozilla.com' directlry. + + `'http://mozilla.com/*'`: matches any URL that starts with 'http://mozilla.com/'. + @prop [query] {string} + A string that matches bookmarks' URL, or title. + @prop [from] {number|date} + Time relative from the [Unix epoch](http://en.wikipedia.org/wiki/Unix_time) that history results should be limited to occuring after. Can accept a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object, or milliseconds from the epoch. Default is to return all items since the epoch (all time). + @prop [to] {number|date} + Time relative from the [Unix epoch](http://en.wikipedia.org/wiki/Unix_time) that history results should be limited to occuring before. Can accept a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object, or milliseconds from the epoch. Default is the current time. + +@param [options] {object} +An `Object` with options for the search query. + @prop [count] {number} + The number of bookmark items to return. If left undefined, no limit is set. + @prop [sort] {string} + A string specifying how the results should be sorted. Possible options are `'title'`, `'date'`, `'url'`, `'visitCount'`, `'keyword'`, `'dateAdded'` and `'lastModified'`. + @prop [descending] {boolean} + A boolean specifying whether the results should be in descending order. By default, results are in ascending order. + + + + + +@class + +The `PlacesEmitter` is not exposed in the module, but returned from the `search` functions. The `PlacesEmitter` inherits from [`event/target`](modules/sdk/event/target.html), and emits `data`, `error`, and `end`. `data` events are emitted for every individual search result found, whereas `end` events are emitted as an aggregate of an entire search, passing in an array of all results into the handler. + + +@event +The `data` event is emitted for every item returned from a search. + +@argument {Object} + This is an object representing a history entry. Contains `url`, `time`, `accessCount` and `title` of the entry. + + + +@event +The `error` event is emitted whenever a search could not be completed. + +@argument {string} + A string indicating the error that occurred. + + + +@event +The `end` event is called when all search results have returned. + +@argument {array} + The value passed into the handler is an array of all entries found in the + history search. Each entry is an object containing the properties + `url`, `time`, `accessCount` and `title`. + + diff --git a/addon-sdk/source/doc/module-source/sdk/request.md b/addon-sdk/source/doc/module-source/sdk/request.md index 3f87aefb3610..a249ddb85ac8 100644 --- a/addon-sdk/source/doc/module-source/sdk/request.md +++ b/addon-sdk/source/doc/module-source/sdk/request.md @@ -2,7 +2,7 @@ - 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/. --> -The `request` module lets you make simple yet powerful network requests. +The `request` module lets you make simple yet powerful network requests. For more advanced usage, check out the [net/xhr](modules/sdk/net/xhr.html) module, based on the browser's [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object. @class diff --git a/addon-sdk/source/doc/module-source/sdk/simple-prefs.md b/addon-sdk/source/doc/module-source/sdk/simple-prefs.md index ecabb6a7e17e..09e2449e6350 100644 --- a/addon-sdk/source/doc/module-source/sdk/simple-prefs.md +++ b/addon-sdk/source/doc/module-source/sdk/simple-prefs.md @@ -100,6 +100,23 @@ These are attributes that all settings *may* have: this may be an integer, string, or boolean value. + + hidden +

A boolean value which, if present and set to true, + means that the setting won't appear in the Add-ons Manager interface, + so users of your add-on won't be able to see or alter it.

+
+{
+    "name": "myHiddenInteger",
+    "type": "integer",
+    "title": "How Many?",
+    "hidden": true
+}
+

Your add-on's code will still be able to access and modify it, + just like any other preference you define.

+ + + ### Setting-Specific Attributes ### diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js index f8742db0affe..2eddaee035de 100644 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ b/addon-sdk/source/lib/sdk/content/worker.js @@ -369,7 +369,7 @@ const WorkerSandbox = EventEmitter.compose({ /** * Message-passing facility for communication between code running * in the content and add-on process. - * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker + * @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html */ const Worker = EventEmitter.compose({ on: Trait.required, diff --git a/addon-sdk/source/lib/sdk/deprecated/api-utils.js b/addon-sdk/source/lib/sdk/deprecated/api-utils.js index 568042b734e2..d4dc835eed95 100644 --- a/addon-sdk/source/lib/sdk/deprecated/api-utils.js +++ b/addon-sdk/source/lib/sdk/deprecated/api-utils.js @@ -5,12 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; - module.metadata = { "stability": "deprecated" }; const memory = require("./memory"); + +const { merge } = require("../util/object"); +const { union } = require("../util/array"); +const { isNil } = require("../lang/type"); + // The possible return values of getTypeOf. const VALID_TYPES = [ "array", @@ -23,6 +27,8 @@ const VALID_TYPES = [ "undefined", ]; +const { isArray } = Array; + /** * Returns a function C that creates instances of privateCtor. C may be called * with or without the new keyword. The prototype of each instance returned @@ -86,6 +92,7 @@ exports.validateOptions = function validateOptions(options, requirements) { let validatedOptions = {}; for (let key in requirements) { + let isOptional = false; let mapThrew = false; let req = requirements[key]; let [optsVal, keyInOpts] = (key in options) ? @@ -103,17 +110,27 @@ exports.validateOptions = function validateOptions(options, requirements) { } } if (req.is) { - // Sanity check the caller's type names. - req.is.forEach(function (typ) { - if (VALID_TYPES.indexOf(typ) < 0) { - let msg = 'Internal error: invalid requirement type "' + typ + '".'; - throw new Error(msg); - } - }); - if (req.is.indexOf(getTypeOf(optsVal)) < 0) - throw new RequirementError(key, req); + let types = req.is; + + if (!isArray(types) && isArray(types.is)) + types = types.is; + + if (isArray(types)) { + isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v)); + + // Sanity check the caller's type names. + types.forEach(function (typ) { + if (VALID_TYPES.indexOf(typ) < 0) { + let msg = 'Internal error: invalid requirement type "' + typ + '".'; + throw new Error(msg); + } + }); + if (types.indexOf(getTypeOf(optsVal)) < 0) + throw new RequirementError(key, req); + } } - if (req.ok && !req.ok(optsVal)) + + if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal))) throw new RequirementError(key, req); if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined)) @@ -142,7 +159,7 @@ let getTypeOf = exports.getTypeOf = function getTypeOf(val) { if (typ === "object") { if (!val) return "null"; - if (Array.isArray(val)) + if (isArray(val)) return "array"; } return typ; @@ -164,3 +181,38 @@ function RequirementError(key, requirement) { this.message = msg; } RequirementError.prototype = Object.create(Error.prototype); + +let string = { is: ['string', 'undefined', 'null'] }; +exports.string = string; + +let number = { is: ['number', 'undefined', 'null'] }; +exports.number = number; + +let boolean = { is: ['boolean', 'undefined', 'null'] }; +exports.boolean = boolean; + +let object = { is: ['object', 'undefined', 'null'] }; +exports.object = object; + +let isTruthyType = type => !(type === 'undefined' || type === 'null'); +let findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v }; + +function required(req) { + let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType); + + return merge({}, req, {is: types}); +} +exports.required = required; + +function optional(req) { + req = merge({is: []}, req); + req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null'); + + return req; +} +exports.optional = optional; + +function either(...types) { + return union.apply(null, types.map(findTypes)); +} +exports.either = either; diff --git a/addon-sdk/source/lib/sdk/net/xhr.js b/addon-sdk/source/lib/sdk/net/xhr.js index f9c7c05217c0..415b9cbf44de 100644 --- a/addon-sdk/source/lib/sdk/net/xhr.js +++ b/addon-sdk/source/lib/sdk/net/xhr.js @@ -4,7 +4,7 @@ "use strict"; module.metadata = { - "stability": "unstable" + "stability": "stable" }; const { deprecateFunction } = require("../util/deprecate"); @@ -33,4 +33,4 @@ function forceAllowThirdPartyCookie(xhr) { exports.forceAllowThirdPartyCookie = forceAllowThirdPartyCookie; // No need to handle add-on unloads as addon/window is closed at unload -// and it will take down all the associated requests. \ No newline at end of file +// and it will take down all the associated requests. diff --git a/addon-sdk/source/lib/sdk/places/events.js b/addon-sdk/source/lib/sdk/places/events.js new file mode 100644 index 000000000000..4703d2a65277 --- /dev/null +++ b/addon-sdk/source/lib/sdk/places/events.js @@ -0,0 +1,121 @@ +/* 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'; + +module.metadata = { + 'stability': 'experimental', + 'engines': { + 'Firefox': '*' + } +}; + +const { Cc, Ci } = require('chrome'); +const { Unknown } = require('../platform/xpcom'); +const { Class } = require('../core/heritage'); +const { merge } = require('../util/object'); +const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] + .getService(Ci.nsINavBookmarksService); +const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] + .getService(Ci.nsINavHistoryService); +const { mapBookmarkItemType } = require('./utils'); +const { EventTarget } = require('../event/target'); +const { emit } = require('../event/core'); + +const emitter = EventTarget(); + +let HISTORY_ARGS = { + onBeginUpdateBatch: [], + onEndUpdateBatch: [], + onClearHistory: [], + onDeleteURI: ['url'], + onDeleteVisits: ['url', 'visitTime'], + onPageChanged: ['url', 'property', 'value'], + onTitleChanged: ['url', 'title'], + onVisit: [ + 'url', 'visitId', 'time', 'sessionId', 'referringId', 'transitionType' + ] +}; + +let HISTORY_EVENTS = { + onBeginUpdateBatch: 'history-start-batch', + onEndUpdateBatch: 'history-end-batch', + onClearHistory: 'history-start-clear', + onDeleteURI: 'history-delete-url', + onDeleteVisits: 'history-delete-visits', + onPageChanged: 'history-page-changed', + onTitleChanged: 'history-title-changed', + onVisit: 'history-visit' +}; + +let BOOKMARK_ARGS = { + onItemAdded: [ + 'id', 'parentId', 'index', 'type', 'url', 'title', 'dateAdded' + ], + onItemChanged: [ + 'id', 'property', null, 'value', 'lastModified', 'type', 'parentId' + ], + onItemMoved: [ + 'id', 'previousParentId', 'previousIndex', 'currentParentId', + 'currentIndex', 'type' + ], + onItemRemoved: ['id', 'parentId', 'index', 'type', 'url'], + onItemVisited: ['id', 'visitId', 'time', 'transitionType', 'url', 'parentId'] +}; + +let BOOKMARK_EVENTS = { + onItemAdded: 'bookmark-item-added', + onItemChanged: 'bookmark-item-changed', + onItemMoved: 'bookmark-item-moved', + onItemRemoved: 'bookmark-item-removed', + onItemVisited: 'bookmark-item-visited', +}; + +function createHandler (type, propNames) { + propNames = propNames || []; + return function (...args) { + let data = propNames.reduce((acc, prop, i) => { + if (prop) + acc[prop] = formatValue(prop, args[i]); + return acc; + }, {}); + + emit(emitter, 'data', { + type: type, + data: data + }); + }; +} + +/* + * Creates an observer, creating handlers based off of + * the `events` names, and ordering arguments from `propNames` hash + */ +function createObserverInstance (events, propNames) { + let definition = Object.keys(events).reduce((prototype, eventName) => { + prototype[eventName] = createHandler(events[eventName], propNames[eventName]); + return prototype; + }, {}); + + return Class(merge(definition, { extends: Unknown }))(); +} + +/* + * Formats `data` based off of the value of `type` + */ +function formatValue (type, data) { + if (type === 'type') + return mapBookmarkItemType(data); + if (type === 'url' && data) + return data.spec; + return data; +} + +let historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS); +historyService.addObserver(historyObserver, false); + +let bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS); +bookmarkService.addObserver(bookmarkObserver, false); + +exports.events = emitter; diff --git a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js index f138bb22e8db..acac020231d5 100644 --- a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js +++ b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js @@ -104,17 +104,22 @@ function saveBookmarkItem (data) { let group = bmsrv.getFolderIdForItem(id); let index = bmsrv.getItemIndex(id); let type = bmsrv.getItemType(id); + let title = typeMap(type) !== 'separator' ? + bmsrv.getItemTitle(id) : + undefined; + let url = typeMap(type) === 'bookmark' ? + bmsrv.getBookmarkURI(id).spec : + undefined; - if (data.url) { + if (url != data.url) bmsrv.changeBookmarkURI(id, newURI(data.url)); - } else if (typeMap(type) === 'bookmark') - data.url = bmsrv.getBookmarkURI(id).spec; + data.url = url; - if (data.title) + if (title != data.title) bmsrv.setItemTitle(id, data.title); else if (typeMap(type) !== 'separator') - data.title = bmsrv.getItemTitle(id); + data.title = title; if (data.group && data.group !== group) bmsrv.moveItem(id, data.group, data.index || -1); @@ -123,7 +128,7 @@ function saveBookmarkItem (data) { // so we don't have to manage the indicies of the siblings bmsrv.moveItem(id, group, data.index); } else if (data.index == null) - data.index = bmsrv.getItemIndex(id); + data.index = index; data.updated = bmsrv.getItemLastModified(data.id); diff --git a/addon-sdk/source/lib/sdk/places/utils.js b/addon-sdk/source/lib/sdk/places/utils.js index 1ce87d717fa0..306902fb472e 100644 --- a/addon-sdk/source/lib/sdk/places/utils.js +++ b/addon-sdk/source/lib/sdk/places/utils.js @@ -235,3 +235,16 @@ function createQueryOptions (type, options) { } exports.createQueryOptions = createQueryOptions; + +function mapBookmarkItemType (type) { + if (typeof type === 'number') { + if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; + if (bmsrv.TYPE_FOLDER === type) return 'group'; + if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; + } else { + if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; + if ('group' === type) return bmsrv.TYPE_FOLDER; + if ('separator' === type) return bmsrv.TYPE_SEPARATOR; + } +} +exports.mapBookmarkItemType = mapBookmarkItemType; diff --git a/addon-sdk/source/lib/sdk/tabs/events.js b/addon-sdk/source/lib/sdk/tabs/events.js index 118231b67150..65650f9dcf71 100644 --- a/addon-sdk/source/lib/sdk/tabs/events.js +++ b/addon-sdk/source/lib/sdk/tabs/events.js @@ -26,7 +26,14 @@ exports.EVENTS = EVENTS; Object.keys(EVENTS).forEach(function(name) { EVENTS[name] = { name: name, - listener: ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1), + listener: createListenerName(name), dom: EVENTS[name] } }); + +function createListenerName (name) { + if (name === 'pageshow') + return 'onPageShow'; + else + return ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1); +} diff --git a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js index 65929c184c19..70a7659beb68 100644 --- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js +++ b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js @@ -33,6 +33,9 @@ const Tab = Class({ // TabReady let onReady = tabInternals.onReady = onTabReady.bind(this); tab.browser.addEventListener(EVENTS.ready.dom, onReady, false); + + let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this); + tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow, false); // TabClose let onClose = tabInternals.onClose = onTabClose.bind(this); @@ -180,8 +183,10 @@ function cleanupTab(tab) { if (tabInternals.tab.browser) { tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false); + tabInternals.tab.browser.removeEventListener(EVENTS.pageshow.dom, tabInternals.onPageShow, false); } tabInternals.onReady = null; + tabInternals.onPageShow = null; tabInternals.window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, tabInternals.onClose, false); tabInternals.onClose = null; rawTabNS(tabInternals.tab).tab = null; @@ -198,6 +203,12 @@ function onTabReady(event) { } } +function onTabPageShow(event) { + let win = event.target.defaultView; + if (win === win.top) + emit(this, 'pageshow', this, event.persisted); +} + // TabClose function onTabClose(event) { let rawTab = getTabForBrowser(event.target); diff --git a/addon-sdk/source/lib/sdk/url.js b/addon-sdk/source/lib/sdk/url.js index 87bca3686974..06a383ca18f6 100644 --- a/addon-sdk/source/lib/sdk/url.js +++ b/addon-sdk/source/lib/sdk/url.js @@ -1,7 +1,6 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; module.metadata = { @@ -277,3 +276,16 @@ let isValidURI = exports.isValidURI = function (uri) { } return true; } + +function isLocalURL(url) { + if (String.indexOf(url, './') === 0) + return true; + + try { + return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1; + } + catch(e) {} + + return false; +} +exports.isLocalURL = isLocalURL; diff --git a/addon-sdk/source/lib/sdk/util/array.js b/addon-sdk/source/lib/sdk/util/array.js index 75ccf9e8630f..198024624c7f 100644 --- a/addon-sdk/source/lib/sdk/util/array.js +++ b/addon-sdk/source/lib/sdk/util/array.js @@ -72,12 +72,22 @@ exports.remove = function remove(array, element) { * Source array. * @returns {Array} */ -exports.unique = function unique(array) { - return array.reduce(function(values, element) { - add(values, element); - return values; +function unique(array) { + return array.reduce(function(result, item) { + add(result, item); + return result; }, []); }; +exports.unique = unique; + +/** + * Produce an array that contains the union: each distinct element from all + * of the passed-in arrays. + */ +function union() { + return unique(Array.concat.apply(null, arguments)); +}; +exports.union = union; exports.flatten = function flatten(array){ var flat = []; diff --git a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js index ba6793d274f8..f5eca6f29740 100644 --- a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js +++ b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js @@ -77,6 +77,9 @@ const Tabs = Class({ if (options.onReady) tab.on('ready', options.onReady); + if (options.onPageShow) + tab.on('pageshow', options.onPageShow); + if (options.onActivate) tab.on('activate', options.onActivate); @@ -131,9 +134,12 @@ function onTabOpen(event) { tab.on('ready', function() emit(gTabs, 'ready', tab)); tab.once('close', onTabClose); + tab.on('pageshow', function(_tab, persisted) + emit(gTabs, 'pageshow', tab, persisted)); + emit(tab, 'open', tab); emit(gTabs, 'open', tab); -}; +} // TabSelect function onTabSelect(event) { @@ -153,10 +159,10 @@ function onTabSelect(event) { emit(t, 'deactivate', t); emit(gTabs, 'deactivate', t); } -}; +} // TabClose function onTabClose(tab) { removeTab(tab); emit(gTabs, EVENTS.close.name, tab); -}; +} diff --git a/addon-sdk/source/python-lib/cuddlefish/__init__.py b/addon-sdk/source/python-lib/cuddlefish/__init__.py index 2672da9acb69..d3826e2fd461 100644 --- a/addon-sdk/source/python-lib/cuddlefish/__init__.py +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py @@ -172,7 +172,7 @@ parser_groups = ( (("", "--strip-sdk",), dict(dest="bundle_sdk", help=("Do not ship SDK modules in the xpi"), action="store_false", - default=True, + default=False, cmds=['run', 'test', 'testex', 'testpkgs', 'testall', 'xpi'])), (("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk", @@ -564,7 +564,7 @@ def initializer(env_root, args, out=sys.stdout, err=sys.stderr): print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' else: print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.' - print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' + print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' return {"result":0, "jid":jid} def buildJID(target_cfg): @@ -593,7 +593,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root); - + # reparse configs with arguments from local.json if config_args: parser_kwargs['arguments'] += config_args diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py index d13f306eda65..66c53cf148de 100755 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py @@ -188,16 +188,13 @@ class Contents(unittest.TestCase): self.failUnlessEqual(e.args[0], 0) zf = zipfile.ZipFile("seven.xpi", "r") names = zf.namelist() - # the first problem found in bug 664840 was that cuddlefish.js - # (the loader) was stripped out on windows, due to a /-vs-\ bug - self.assertIn("resources/addon-sdk/lib/sdk/loader/cuddlefish.js", names) - # the second problem found in bug 664840 was that an addon + # problem found in bug 664840 was that an addon # without an explicit tests/ directory would copy all files from # the package into a bogus JID-PKGNAME-tests/ directory, so check # for that testfiles = [fn for fn in names if "seven/tests" in fn] self.failUnlessEqual([], testfiles) - # the third problem was that data files were being stripped from + # another problem was that data files were being stripped from # the XPI. Note that data/ is only supposed to be included if a # module that actually gets used does a require("self") . self.assertIn("resources/seven/data/text.data", diff --git a/addon-sdk/source/python-lib/mozrunner/__init__.py b/addon-sdk/source/python-lib/mozrunner/__init__.py index 2e5c57abd908..87c2c320fc58 100644 --- a/addon-sdk/source/python-lib/mozrunner/__init__.py +++ b/addon-sdk/source/python-lib/mozrunner/__init__.py @@ -406,7 +406,8 @@ class Runner(object): def find_binary(self): """Finds the binary for self.names if one was not provided.""" binary = None - if sys.platform in ('linux2', 'sunos5', 'solaris'): + if sys.platform in ('linux2', 'sunos5', 'solaris') \ + or sys.platform.startswith('freebsd'): for name in reversed(self.names): binary = findInPath(name) elif os.name == 'nt' or sys.platform == 'cygwin': @@ -578,7 +579,8 @@ class FirefoxRunner(Runner): def names(self): if sys.platform == 'darwin': return ['firefox', 'nightly', 'shiretoko'] - if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): + if sys.platform in ('linux2', 'sunos5', 'solaris') \ + or sys.platform.startswith('freebsd'): return ['firefox', 'mozilla-firefox', 'iceweasel'] if os.name == 'nt' or sys.platform == 'cygwin': return ['firefox'] diff --git a/addon-sdk/source/python-lib/mozrunner/killableprocess.py b/addon-sdk/source/python-lib/mozrunner/killableprocess.py index 892ed87fc85b..92f7ac3779fe 100644 --- a/addon-sdk/source/python-lib/mozrunner/killableprocess.py +++ b/addon-sdk/source/python-lib/mozrunner/killableprocess.py @@ -257,7 +257,8 @@ class Popen(subprocess.Popen): self.kill(group) else: - if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): + if sys.platform in ('linux2', 'sunos5', 'solaris') \ + or sys.platform.startswith('freebsd'): def group_wait(timeout): try: os.waitpid(self.pid, 0) diff --git a/addon-sdk/source/test/addons/private-browsing-supported/main.js b/addon-sdk/source/test/addons/private-browsing-supported/main.js index 34b9f588b48b..9e8ef5d3140b 100644 --- a/addon-sdk/source/test/addons/private-browsing-supported/main.js +++ b/addon-sdk/source/test/addons/private-browsing-supported/main.js @@ -10,15 +10,20 @@ const { isGlobalPBSupported } = require('sdk/private-browsing/utils'); merge(module.exports, require('./test-tabs'), require('./test-page-mod'), - require('./test-selection'), - require('./test-panel'), require('./test-private-browsing'), isGlobalPBSupported ? require('./test-global-private-browsing') : {} ); // Doesn't make sense to test window-utils and windows on fennec, -// as there is only one window which is never private -if (!app.is('Fennec')) - merge(module.exports, require('./test-windows')); +// as there is only one window which is never private. Also ignore +// unsupported modules (panel, selection) +if (!app.is('Fennec')) { + merge(module.exports, + require('./test-selection'), + require('./test-panel'), + require('./test-window-tabs'), + require('./test-windows') + ); +} require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js b/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js index dac987713789..dbc0385bef7b 100644 --- a/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js +++ b/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js @@ -1,12 +1,9 @@ 'use strict'; const tabs = require('sdk/tabs'); -const { is } = require('sdk/system/xul-app'); const { isPrivate } = require('sdk/private-browsing'); const pbUtils = require('sdk/private-browsing/utils'); const { getOwnerWindow } = require('sdk/private-browsing/window/utils'); -const { promise: windowPromise, close, focus } = require('sdk/window/helpers'); -const { getMostRecentBrowserWindow } = require('sdk/window/utils'); exports.testPrivateTabsAreListed = function (assert, done) { let originalTabCount = tabs.length; @@ -32,82 +29,5 @@ exports.testPrivateTabsAreListed = function (assert, done) { tab.close(done); } }); -} +}; -exports.testOpenTabWithPrivateActiveWindowNoIsPrivateOption = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.ok(isPrivate(window), 'new window is private'); - - tabs.open({ - url: 'about:blank', - onOpen: function(tab) { - assert.ok(isPrivate(tab), 'new tab is private'); - assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} - -exports.testOpenTabWithNonPrivateActiveWindowNoIsPrivateOption = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.equal(isPrivate(window), false, 'new window is not private'); - - tabs.open({ - url: 'about:blank', - onOpen: function(tab) { - assert.equal(isPrivate(tab), false, 'new tab is not private'); - assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} - -exports.testOpenTabWithPrivateActiveWindowWithIsPrivateOptionTrue = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.ok(isPrivate(window), 'new window is private'); - - tabs.open({ - url: 'about:blank', - isPrivate: true, - onOpen: function(tab) { - assert.ok(isPrivate(tab), 'new tab is private'); - assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} - -exports.testOpenTabWithNonPrivateActiveWindowWithIsPrivateOptionFalse = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.equal(isPrivate(window), false, 'new window is not private'); - - tabs.open({ - url: 'about:blank', - isPrivate: false, - onOpen: function(tab) { - assert.equal(isPrivate(tab), false, 'new tab is not private'); - assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js b/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js new file mode 100644 index 000000000000..05e787a4f3b6 --- /dev/null +++ b/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js @@ -0,0 +1,85 @@ +'use strict'; + +const tabs = require('sdk/tabs'); +const { isPrivate } = require('sdk/private-browsing'); +const { getOwnerWindow } = require('sdk/private-browsing/window/utils'); +const { promise: windowPromise, close, focus } = require('sdk/window/helpers'); +const { getMostRecentBrowserWindow } = require('sdk/window/utils'); + +exports.testOpenTabWithPrivateActiveWindowNoIsPrivateOption = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.ok(isPrivate(window), 'new window is private'); + + tabs.open({ + url: 'about:blank', + onOpen: function(tab) { + assert.ok(isPrivate(tab), 'new tab is private'); + assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} + +exports.testOpenTabWithNonPrivateActiveWindowNoIsPrivateOption = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.equal(isPrivate(window), false, 'new window is not private'); + + tabs.open({ + url: 'about:blank', + onOpen: function(tab) { + assert.equal(isPrivate(tab), false, 'new tab is not private'); + assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} + +exports.testOpenTabWithPrivateActiveWindowWithIsPrivateOptionTrue = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.ok(isPrivate(window), 'new window is private'); + + tabs.open({ + url: 'about:blank', + isPrivate: true, + onOpen: function(tab) { + assert.ok(isPrivate(tab), 'new tab is private'); + assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} + +exports.testOpenTabWithNonPrivateActiveWindowWithIsPrivateOptionFalse = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.equal(isPrivate(window), false, 'new window is not private'); + + tabs.open({ + url: 'about:blank', + isPrivate: false, + onOpen: function(tab) { + assert.equal(isPrivate(tab), false, 'new tab is not private'); + assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} diff --git a/addon-sdk/source/test/addons/privileged-panel/main.js b/addon-sdk/source/test/addons/privileged-panel/main.js index c010de9242be..8992a0a39244 100644 --- a/addon-sdk/source/test/addons/privileged-panel/main.js +++ b/addon-sdk/source/test/addons/privileged-panel/main.js @@ -4,10 +4,14 @@ "use strict"; -const { Panel } = require("sdk/panel") -const { data } = require("sdk/self") +const app = require("sdk/system/xul-app"); + +exports["test addon globa"] = app.is("Firefox") ? testAddonGlobal : unsupported; + +function testAddonGlobal (assert, done) { + const { Panel } = require("sdk/panel") + const { data } = require("sdk/self") -exports["test addon global"] = function(assert, done) { let panel = Panel({ contentURL: //"data:text/html,now?", data.url("./index.html"), @@ -17,10 +21,14 @@ exports["test addon global"] = function(assert, done) { done(); }, onError: function(error) { - asser.fail(Error("failed to recieve message")); + assert.fail(Error("failed to recieve message")); done(); } }); }; +function unsupported (assert) { + assert.pass("privileged-panel unsupported on platform"); +} + require("sdk/test/runner").runTestsFromModule(module); diff --git a/addon-sdk/source/test/favicon-helpers.js b/addon-sdk/source/test/favicon-helpers.js index 1be7592b8846..3656ae6748b1 100644 --- a/addon-sdk/source/test/favicon-helpers.js +++ b/addon-sdk/source/test/favicon-helpers.js @@ -13,42 +13,18 @@ const basePath = pathFor('ProfD'); const { atob } = Cu.import("resource://gre/modules/Services.jsm", {}); const historyService = Cc["@mozilla.org/browser/nav-history-service;1"] .getService(Ci.nsINavHistoryService); -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -const ObserverShimMethods = ['onBeginUpdateBatch', 'onEndUpdateBatch', - 'onVisit', 'onTitleChanged', 'onDeleteURI', 'onClearHistory', - 'onPageChanged', 'onDeleteVisits']; +const { events } = require('sdk/places/events'); -/* - * Shims NavHistoryObserver - */ - -let noop = function () {} -let NavHistoryObserver = function () {}; -ObserverShimMethods.forEach(function (method) { - NavHistoryObserver.prototype[method] = noop; -}); -NavHistoryObserver.prototype.QueryInterface = XPCOMUtils.generateQI([ - Ci.nsINavHistoryObserver -]); - -/* - * Uses history observer to watch for an onPageChanged event, - * which detects when a favicon is updated in the registry. - */ -function onFaviconChange (uri, callback) { - let observer = Object.create(NavHistoryObserver.prototype, { - onPageChanged: { - value: function onPageChanged(aURI, aWhat, aValue, aGUID) { - if (aWhat !== Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) - return; - if (aURI.spec !== uri) - return; - historyService.removeObserver(this); - callback(aValue); - } - } - }); - historyService.addObserver(observer, false); +function onFaviconChange (url, callback) { + function handler ({data, type}) { + if (type !== 'history-page-changed' || + data.url !== url || + data.property !== Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) + return; + events.off('data', handler); + callback(data.value); + } + events.on('data', handler); } exports.onFaviconChange = onFaviconChange; diff --git a/addon-sdk/source/test/places-helper.js b/addon-sdk/source/test/places-helper.js index 8ea0af17c8ae..8107b4eae0b5 100644 --- a/addon-sdk/source/test/places-helper.js +++ b/addon-sdk/source/test/places-helper.js @@ -106,6 +106,13 @@ function addVisits (urls) { } exports.addVisits = addVisits; +function removeVisits (urls) { + [].concat(urls).map(url => { + hsrv.removePage(newURI(url)); + }); +} +exports.removeVisits = removeVisits; + // Creates a mozIVisitInfo object function createVisit (url) { let place = {} diff --git a/addon-sdk/source/test/tabs/test-firefox-tabs.js b/addon-sdk/source/test/tabs/test-firefox-tabs.js index 1d5346ac783a..fd59222719fc 100644 --- a/addon-sdk/source/test/tabs/test-firefox-tabs.js +++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js @@ -949,40 +949,6 @@ exports.testOnLoadEventWithImage = function(test) { }); }; -exports.testOnPageShowEvent = function (test) { - test.waitUntilDone(); - - let firstUrl = 'data:text/html;charset=utf-8,First'; - let secondUrl = 'data:text/html;charset=utf-8,Second'; - - openBrowserWindow(function(window, browser) { - let counter = 0; - tabs.on('pageshow', function onPageShow(tab, persisted) { - counter++; - if (counter === 1) { - test.assert(!persisted, 'page should not be cached on initial load'); - tab.url = secondUrl; - } - else if (counter === 2) { - test.assert(!persisted, 'second test page should not be cached either'); - tab.attach({ - contentScript: 'setTimeout(function () { window.history.back(); }, 0)' - }); - } - else { - test.assert(persisted, 'when we get back to the fist page, it has to' + - 'come from cache'); - tabs.removeListener('pageshow', onPageShow); - closeBrowserWindow(window, function() test.done()); - } - }); - - tabs.open({ - url: firstUrl - }); - }); -}; - exports.testFaviconGetterDeprecation = function (test) { const { LoaderWithHookedConsole } = require("sdk/test/loader"); let { loader, messages } = LoaderWithHookedConsole(module); diff --git a/addon-sdk/source/test/test-api-utils.js b/addon-sdk/source/test/test-api-utils.js index f0a0849ac4b4..f26682b6f989 100644 --- a/addon-sdk/source/test/test-api-utils.js +++ b/addon-sdk/source/test/test-api-utils.js @@ -6,92 +6,94 @@ const apiUtils = require("sdk/deprecated/api-utils"); -exports.testPublicConstructor = function (test) { +exports.testPublicConstructor = function (assert) { function PrivateCtor() {} PrivateCtor.prototype = {}; let PublicCtor = apiUtils.publicConstructor(PrivateCtor); - test.assert( + assert.ok( PrivateCtor.prototype.isPrototypeOf(PublicCtor.prototype), "PrivateCtor.prototype should be prototype of PublicCtor.prototype" ); function testObj(useNew) { let obj = useNew ? new PublicCtor() : PublicCtor(); - test.assert(obj instanceof PublicCtor, + assert.ok(obj instanceof PublicCtor, "Object should be instance of PublicCtor"); - test.assert(obj instanceof PrivateCtor, + assert.ok(obj instanceof PrivateCtor, "Object should be instance of PrivateCtor"); - test.assert(PublicCtor.prototype.isPrototypeOf(obj), + assert.ok(PublicCtor.prototype.isPrototypeOf(obj), "PublicCtor's prototype should be prototype of object"); - test.assertEqual(obj.constructor, PublicCtor, + assert.equal(obj.constructor, PublicCtor, "Object constructor should be PublicCtor"); } testObj(true); testObj(false); }; -exports.testValidateOptionsEmpty = function (test) { +exports.testValidateOptionsEmpty = function (assert) { let val = apiUtils.validateOptions(null, {}); - assertObjsEqual(test, val, {}); + + assert.deepEqual(val, {}); val = apiUtils.validateOptions(null, { foo: {} }); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); val = apiUtils.validateOptions({}, {}); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); val = apiUtils.validateOptions({}, { foo: {} }); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); }; -exports.testValidateOptionsNonempty = function (test) { +exports.testValidateOptionsNonempty = function (assert) { let val = apiUtils.validateOptions({ foo: 123 }, {}); - assertObjsEqual(test, val, {}); + assert.deepEqual(val, {}); val = apiUtils.validateOptions({ foo: 123, bar: 456 }, { foo: {}, bar: {}, baz: {} }); - assertObjsEqual(test, val, { foo: 123, bar: 456 }); + + assert.deepEqual(val, { foo: 123, bar: 456 }); }; -exports.testValidateOptionsMap = function (test) { +exports.testValidateOptionsMap = function (assert) { let val = apiUtils.validateOptions({ foo: 3, bar: 2 }, { foo: { map: function (v) v * v }, bar: { map: function (v) undefined } }); - assertObjsEqual(test, val, { foo: 9, bar: undefined }); + assert.deepEqual(val, { foo: 9, bar: undefined }); }; -exports.testValidateOptionsMapException = function (test) { +exports.testValidateOptionsMapException = function (assert) { let val = apiUtils.validateOptions({ foo: 3 }, { foo: { map: function () { throw new Error(); }} }); - assertObjsEqual(test, val, { foo: 3 }); + assert.deepEqual(val, { foo: 3 }); }; -exports.testValidateOptionsOk = function (test) { +exports.testValidateOptionsOk = function (assert) { let val = apiUtils.validateOptions({ foo: 3, bar: 2, baz: 1 }, { foo: { ok: function (v) v }, bar: { ok: function (v) v } }); - assertObjsEqual(test, val, { foo: 3, bar: 2 }); + assert.deepEqual(val, { foo: 3, bar: 2 }); - test.assertRaises( + assert.throws( function () apiUtils.validateOptions({ foo: 2, bar: 2 }, { bar: { ok: function (v) v > 2 } }), - 'The option "bar" is invalid.', + /^The option "bar" is invalid/, "ok should raise exception on invalid option" ); - test.assertRaises( + assert.throws( function () apiUtils.validateOptions(null, { foo: { ok: function (v) v }}), - 'The option "foo" is invalid.', + /^The option "foo" is invalid/, "ok should raise exception on invalid option" ); }; -exports.testValidateOptionsIs = function (test) { +exports.testValidateOptionsIs = function (assert) { let opts = { array: [], boolean: true, @@ -114,18 +116,137 @@ exports.testValidateOptionsIs = function (test) { undef2: { is: ["undefined"] } }; let val = apiUtils.validateOptions(opts, requirements); - assertObjsEqual(test, val, opts); + assert.deepEqual(val, opts); - test.assertRaises( + assert.throws( function () apiUtils.validateOptions(null, { foo: { is: ["object", "number"] } }), - 'The option "foo" must be one of the following types: object, number', + /^The option "foo" must be one of the following types: object, number/, "Invalid type should raise exception" ); }; -exports.testValidateOptionsMapIsOk = function (test) { +exports.testValidateOptionsIsWithExportedValue = function (assert) { + let { string, number, boolean, object } = apiUtils; + + let opts = { + boolean: true, + number: 1337, + object: {}, + string: "foo" + }; + let requirements = { + string: { is: string }, + number: { is: number }, + boolean: { is: boolean }, + object: { is: object } + }; + let val = apiUtils.validateOptions(opts, requirements); + assert.deepEqual(val, opts); + + // Test the types are optional by default + val = apiUtils.validateOptions({foo: 'bar'}, requirements); + assert.deepEqual(val, {}); +}; + +exports.testValidateOptionsIsWithEither = function (assert) { + let { string, number, boolean, either } = apiUtils; + let text = { is: either(string, number) }; + + let requirements = { + text: text, + boolOrText: { is: either(text, boolean) } + }; + + let val = apiUtils.validateOptions({text: 12}, requirements); + assert.deepEqual(val, {text: 12}); + + val = apiUtils.validateOptions({text: "12"}, requirements); + assert.deepEqual(val, {text: "12"}); + + val = apiUtils.validateOptions({boolOrText: true}, requirements); + assert.deepEqual(val, {boolOrText: true}); + + val = apiUtils.validateOptions({boolOrText: "true"}, requirements); + assert.deepEqual(val, {boolOrText: "true"}); + + val = apiUtils.validateOptions({boolOrText: 1}, requirements); + assert.deepEqual(val, {boolOrText: 1}); + + assert.throws( + () => apiUtils.validateOptions({text: true}, requirements), + /^The option "text" must be one of the following types/, + "Invalid type should raise exception" + ); + + assert.throws( + () => apiUtils.validateOptions({boolOrText: []}, requirements), + /^The option "boolOrText" must be one of the following types/, + "Invalid type should raise exception" + ); +}; + +exports.testValidateOptionsWithRequiredAndOptional = function (assert) { + let { string, number, required, optional } = apiUtils; + + let opts = { + number: 1337, + string: "foo" + }; + + let requirements = { + string: required(string), + number: number + }; + + let val = apiUtils.validateOptions(opts, requirements); + assert.deepEqual(val, opts); + + val = apiUtils.validateOptions({string: "foo"}, requirements); + assert.deepEqual(val, {string: "foo"}); + + assert.throws( + () => apiUtils.validateOptions({number: 10}, requirements), + /^The option "string" must be one of the following types/, + "Invalid type should raise exception" + ); + + // Makes string optional + requirements.string = optional(requirements.string); + + val = apiUtils.validateOptions({number: 10}, requirements), + assert.deepEqual(val, {number: 10}); + +}; + + + +exports.testValidateOptionsWithExportedValue = function (assert) { + let { string, number, boolean, object } = apiUtils; + + let opts = { + boolean: true, + number: 1337, + object: {}, + string: "foo" + }; + let requirements = { + string: string, + number: number, + boolean: boolean, + object: object + }; + let val = apiUtils.validateOptions(opts, requirements); + assert.deepEqual(val, opts); + + // Test the types are optional by default + val = apiUtils.validateOptions({foo: 'bar'}, requirements); + assert.deepEqual(val, {}); +}; + + +exports.testValidateOptionsMapIsOk = function (assert) { let [map, is, ok] = [false, false, false]; let val = apiUtils.validateOptions({ foo: 1337 }, { foo: { @@ -134,48 +255,48 @@ exports.testValidateOptionsMapIsOk = function (test) { ok: function (v) v.length > 0 } }); - assertObjsEqual(test, val, { foo: "1337" }); + assert.deepEqual(val, { foo: "1337" }); let requirements = { foo: { is: ["object"], - ok: function () test.fail("is should have caused us to throw by now") + ok: function () assert.fail("is should have caused us to throw by now") } }; - test.assertRaises( + assert.throws( function () apiUtils.validateOptions(null, requirements), - 'The option "foo" must be one of the following types: object', + /^The option "foo" must be one of the following types: object/, "is should be used before ok is called" ); }; -exports.testValidateOptionsErrorMsg = function (test) { - test.assertRaises( +exports.testValidateOptionsErrorMsg = function (assert) { + assert.throws( function () apiUtils.validateOptions(null, { foo: { ok: function (v) v, msg: "foo!" } }), - "foo!", + /^foo!/, "ok should raise exception with customized message" ); }; -exports.testValidateMapWithMissingKey = function (test) { +exports.testValidateMapWithMissingKey = function (assert) { let val = apiUtils.validateOptions({ }, { foo: { map: function (v) v || "bar" } }); - assertObjsEqual(test, val, { foo: "bar" }); + assert.deepEqual(val, { foo: "bar" }); val = apiUtils.validateOptions({ }, { foo: { map: function (v) { throw "bar" } } }); - assertObjsEqual(test, val, { }); + assert.deepEqual(val, { }); }; -exports.testValidateMapWithMissingKeyAndThrown = function (test) { +exports.testValidateMapWithMissingKeyAndThrown = function (assert) { let val = apiUtils.validateOptions({}, { bar: { map: function(v) { throw "bar" } @@ -184,10 +305,10 @@ exports.testValidateMapWithMissingKeyAndThrown = function (test) { map: function(v) "foo" } }); - assertObjsEqual(test, val, { baz: "foo" }); + assert.deepEqual(val, { baz: "foo" }); }; -exports.testAddIterator = function testAddIterator(test) { +exports.testAddIterator = function testAddIterator (assert) { let obj = {}; let keys = ["foo", "bar", "baz"]; let vals = [1, 2, 3]; @@ -203,34 +324,20 @@ exports.testAddIterator = function testAddIterator(test) { let keysItr = []; for (let key in obj) keysItr.push(key); - test.assertEqual(keysItr.length, keys.length, + + assert.equal(keysItr.length, keys.length, "the keys iterator returns the correct number of items"); for (let i = 0; i < keys.length; i++) - test.assertEqual(keysItr[i], keys[i], "the key is correct"); + assert.equal(keysItr[i], keys[i], "the key is correct"); let valsItr = []; for each (let val in obj) valsItr.push(val); - test.assertEqual(valsItr.length, vals.length, + assert.equal(valsItr.length, vals.length, "the vals iterator returns the correct number of items"); for (let i = 0; i < vals.length; i++) - test.assertEqual(valsItr[i], vals[i], "the val is correct"); + assert.equal(valsItr[i], vals[i], "the val is correct"); }; -function assertObjsEqual(test, obj1, obj2) { - var items = 0; - for (let key in obj1) { - items++; - test.assert(key in obj2, "obj1 key should be present in obj2"); - test.assertEqual(obj2[key], obj1[key], "obj1 value should match obj2 value"); - } - for (let key in obj2) { - items++; - test.assert(key in obj1, "obj2 key should be present in obj1"); - test.assertEqual(obj1[key], obj2[key], "obj2 value should match obj1 value"); - } - if (!items) - test.assertEqual(JSON.stringify(obj1), JSON.stringify(obj2), - "obj1 should have same JSON representation as obj2"); -} +require('test').run(exports); diff --git a/addon-sdk/source/test/test-array.js b/addon-sdk/source/test/test-array.js index af26770edbbe..8a9cb4402be8 100644 --- a/addon-sdk/source/test/test-array.js +++ b/addon-sdk/source/test/test-array.js @@ -5,67 +5,67 @@ const array = require('sdk/util/array'); -exports.testHas = function(test) { +exports.testHas = function(assert) { var testAry = [1, 2, 3]; - test.assertEqual(array.has([1, 2, 3], 1), true); - test.assertEqual(testAry.length, 3); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); - test.assertEqual(testAry[2], 3); - test.assertEqual(array.has(testAry, 2), true); - test.assertEqual(array.has(testAry, 3), true); - test.assertEqual(array.has(testAry, 4), false); - test.assertEqual(array.has(testAry, '1'), false); + assert.equal(array.has([1, 2, 3], 1), true); + assert.equal(testAry.length, 3); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); + assert.equal(testAry[2], 3); + assert.equal(array.has(testAry, 2), true); + assert.equal(array.has(testAry, 3), true); + assert.equal(array.has(testAry, 4), false); + assert.equal(array.has(testAry, '1'), false); }; -exports.testHasAny = function(test) { +exports.testHasAny = function(assert) { var testAry = [1, 2, 3]; - test.assertEqual(array.hasAny([1, 2, 3], [1]), true); - test.assertEqual(array.hasAny([1, 2, 3], [1, 5]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 1]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 2]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 3]), true); - test.assertEqual(array.hasAny([1, 2, 3], [5, 4]), false); - test.assertEqual(testAry.length, 3); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); - test.assertEqual(testAry[2], 3); - test.assertEqual(array.hasAny(testAry, [2]), true); - test.assertEqual(array.hasAny(testAry, [3]), true); - test.assertEqual(array.hasAny(testAry, [4]), false); - test.assertEqual(array.hasAny(testAry), false); - test.assertEqual(array.hasAny(testAry, '1'), false); + assert.equal(array.hasAny([1, 2, 3], [1]), true); + assert.equal(array.hasAny([1, 2, 3], [1, 5]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 1]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 2]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 3]), true); + assert.equal(array.hasAny([1, 2, 3], [5, 4]), false); + assert.equal(testAry.length, 3); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); + assert.equal(testAry[2], 3); + assert.equal(array.hasAny(testAry, [2]), true); + assert.equal(array.hasAny(testAry, [3]), true); + assert.equal(array.hasAny(testAry, [4]), false); + assert.equal(array.hasAny(testAry), false); + assert.equal(array.hasAny(testAry, '1'), false); }; -exports.testAdd = function(test) { +exports.testAdd = function(assert) { var testAry = [1]; - test.assertEqual(array.add(testAry, 1), false); - test.assertEqual(testAry.length, 1); - test.assertEqual(testAry[0], 1); - test.assertEqual(array.add(testAry, 2), true); - test.assertEqual(testAry.length, 2); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); + assert.equal(array.add(testAry, 1), false); + assert.equal(testAry.length, 1); + assert.equal(testAry[0], 1); + assert.equal(array.add(testAry, 2), true); + assert.equal(testAry.length, 2); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); }; -exports.testRemove = function(test) { +exports.testRemove = function(assert) { var testAry = [1, 2]; - test.assertEqual(array.remove(testAry, 3), false); - test.assertEqual(testAry.length, 2); - test.assertEqual(testAry[0], 1); - test.assertEqual(testAry[1], 2); - test.assertEqual(array.remove(testAry, 2), true); - test.assertEqual(testAry.length, 1); - test.assertEqual(testAry[0], 1); + assert.equal(array.remove(testAry, 3), false); + assert.equal(testAry.length, 2); + assert.equal(testAry[0], 1); + assert.equal(testAry[1], 2); + assert.equal(array.remove(testAry, 2), true); + assert.equal(testAry.length, 1); + assert.equal(testAry[0], 1); }; -exports.testFlatten = function(test) { - test.assertEqual(array.flatten([1, 2, 3]).length, 3); - test.assertEqual(array.flatten([1, [2, 3]]).length, 3); - test.assertEqual(array.flatten([1, [2, [3]]]).length, 3); - test.assertEqual(array.flatten([[1], [[2, [3]]]]).length, 3); +exports.testFlatten = function(assert) { + assert.equal(array.flatten([1, 2, 3]).length, 3); + assert.equal(array.flatten([1, [2, 3]]).length, 3); + assert.equal(array.flatten([1, [2, [3]]]).length, 3); + assert.equal(array.flatten([[1], [[2, [3]]]]).length, 3); }; -exports.testUnique = function(test) { +exports.testUnique = function(assert) { var Class = function () {}; var A = {}; var B = new Class(); @@ -73,23 +73,31 @@ exports.testUnique = function(test) { var D = {}; var E = new Class(); - compareArray(array.unique([1,2,3,1,2]), [1,2,3]); - compareArray(array.unique([1,1,1,4,9,5,5]), [1,4,9,5]); - compareArray(array.unique([A, A, A, B, B, D]), [A,B,D]); - compareArray(array.unique([A, D, A, E, E, D, A, A, C]), [A, D, E, C]) - - function compareArray (a, b) { - test.assertEqual(a.length, b.length); - for (let i = 0; i < a.length; i++) { - test.assertEqual(a[i], b[i]); - } - } + assert.deepEqual(array.unique([1,2,3,1,2]), [1,2,3]); + assert.deepEqual(array.unique([1,1,1,4,9,5,5]), [1,4,9,5]); + assert.deepEqual(array.unique([A, A, A, B, B, D]), [A,B,D]); + assert.deepEqual(array.unique([A, D, A, E, E, D, A, A, C]), [A, D, E, C]) }; -exports.testFind = function(test) { +exports.testUnion = function(assert) { + var Class = function () {}; + var A = {}; + var B = new Class(); + var C = [ 1, 2, 3 ]; + var D = {}; + var E = new Class(); + + assert.deepEqual(array.union([1, 2, 3],[7, 1, 2]), [1, 2, 3, 7]); + assert.deepEqual(array.union([1, 1, 1, 4, 9, 5, 5], [10, 1, 5]), [1, 4, 9, 5, 10]); + assert.deepEqual(array.union([A, B], [A, D]), [A, B, D]); + assert.deepEqual(array.union([A, D], [A, E], [E, D, A], [A, C]), [A, D, E, C]); +}; + +exports.testFind = function(assert) { let isOdd = (x) => x % 2; - test.assertEqual(array.find([2, 4, 5, 7, 8, 9], isOdd), 5); - test.assertEqual(array.find([2, 4, 6, 8], isOdd), undefined); - test.assertEqual(array.find([2, 4, 6, 8], isOdd, null), null); + assert.equal(array.find([2, 4, 5, 7, 8, 9], isOdd), 5); + assert.equal(array.find([2, 4, 6, 8], isOdd), undefined); + assert.equal(array.find([2, 4, 6, 8], isOdd, null), null); }; +require('test').run(exports); diff --git a/addon-sdk/source/test/test-browser-events.js b/addon-sdk/source/test/test-browser-events.js index afa7180977d5..b4d8a1b016da 100644 --- a/addon-sdk/source/test/test-browser-events.js +++ b/addon-sdk/source/test/test-browser-events.js @@ -4,6 +4,12 @@ "use strict"; +module.metadata = { + engines: { + "Firefox": "*" + } +}; + const { Loader } = require("sdk/test/loader"); const { open, getMostRecentBrowserWindow, getOuterId } = require("sdk/window/utils"); const { setTimeout } = require("sdk/timers"); diff --git a/addon-sdk/source/test/test-content-events.js b/addon-sdk/source/test/test-content-events.js index 43d4fadf9847..f4a7b1644792 100644 --- a/addon-sdk/source/test/test-content-events.js +++ b/addon-sdk/source/test/test-content-events.js @@ -44,13 +44,17 @@ exports["test multiple tabs"] = function(assert, done) { on(events, "data", function({type, target, timeStamp}) { // ignore about:blank pages and *-document-global-created // events that are not very consistent. + // ignore http:// requests, as Fennec's `about:home` page + // displays add-ons a user could install if (target.URL !== "about:blank" && + target.URL !== "about:home" && + !target.URL.match(/^https?:\/\//i) && type !== "chrome-document-global-created" && type !== "content-document-global-created") actual.push(type + " -> " + target.URL) }); - let window = getMostRecentBrowserWindow(); + let window = getMostRecentBrowserWindow(); let firstTab = open("data:text/html,first-tab", window); when("pageshow", firstTab). diff --git a/addon-sdk/source/test/test-path.js b/addon-sdk/source/test/test-path.js index 9e9cb5c53190..f0b9e06cb0a2 100644 --- a/addon-sdk/source/test/test-path.js +++ b/addon-sdk/source/test/test-path.js @@ -429,8 +429,3 @@ if (isWindows) { }; require('test').run(exports); - -// Test disabled on OSX because of bug 891698 -if (require("sdk/system/runtime").OS == "Darwin") - module.exports = {}; - diff --git a/addon-sdk/source/test/test-places-events.js b/addon-sdk/source/test/test-places-events.js new file mode 100644 index 000000000000..67f82188d8fd --- /dev/null +++ b/addon-sdk/source/test/test-places-events.js @@ -0,0 +1,292 @@ +/* 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'; + +module.metadata = { + 'engines': { + 'Firefox': '*' + } +}; + +const { Cc, Ci } = require('chrome'); +const { defer, all } = require('sdk/core/promise'); +const { filter } = require('sdk/event/utils'); +const { on, off } = require('sdk/event/core'); +const { events } = require('sdk/places/events'); +const { setTimeout } = require('sdk/timers'); +const { before, after } = require('sdk/test/utils'); +const { + search +} = require('sdk/places/history'); +const { + invalidResolve, invalidReject, createTree, createBookmark, + compareWithHost, addVisits, resetPlaces, createBookmarkItem, + removeVisits +} = require('./places-helper'); +const { save, MENU, UNSORTED } = require('sdk/places/bookmarks'); +const { promisedEmitter } = require('sdk/places/utils'); + +exports['test bookmark-item-added'] = function (assert, done) { + function handler ({type, data}) { + if (type !== 'bookmark-item-added') return; + if (data.title !== 'bookmark-added-title') return; + + assert.equal(type, 'bookmark-item-added', 'correct type in bookmark-added event'); + assert.equal(data.type, 'bookmark', 'correct data in bookmark-added event'); + assert.ok(data.id != null, 'correct data in bookmark-added event'); + assert.ok(data.parentId != null, 'correct data in bookmark-added event'); + assert.ok(data.index != null, 'correct data in bookmark-added event'); + assert.equal(data.url, 'http://moz.com/', 'correct data in bookmark-added event'); + assert.ok(data.dateAdded != null, 'correct data in bookmark-added event'); + events.off('data', handler); + done(); + } + events.on('data', handler); + createBookmark({ title: 'bookmark-added-title' }); +}; + +exports['test bookmark-item-changed'] = function (assert, done) { + let id; + let complete = makeCompleted(done); + function handler ({type, data}) { + if (type !== 'bookmark-item-changed') return; + if (data.id !== id) return; + assert.equal(type, 'bookmark-item-changed', + 'correct type in bookmark-item-changed event'); + assert.equal(data.type, 'bookmark', + 'correct data in bookmark-item-changed event'); + assert.equal(data.property, 'title', + 'correct property in bookmark-item-changed event'); + assert.equal(data.value, 'bookmark-changed-title-2', + 'correct value in bookmark-item-changed event'); + assert.ok(data.id === id, 'correct id in bookmark-item-changed event'); + assert.ok(data.parentId != null, 'correct data in bookmark-added event'); + + events.off('data', handler); + complete(); + } + events.on('data', handler); + + createBookmarkItem({ title: 'bookmark-changed-title' }).then(item => { + id = item.id; + item.title = 'bookmark-changed-title-2'; + return saveP(item); + }).then(complete); +}; + +exports['test bookmark-item-moved'] = function (assert, done) { + let id; + let complete = makeCompleted(done); + function handler ({type, data}) { + if (type !== 'bookmark-item-moved') return; + if (data.id !== id) return; + assert.equal(type, 'bookmark-item-moved', + 'correct type in bookmark-item-moved event'); + assert.equal(data.type, 'bookmark', + 'correct data in bookmark-item-moved event'); + assert.ok(data.id === id, 'correct id in bookmark-item-moved event'); + assert.equal(data.previousParentId, UNSORTED.id, + 'correct previousParentId'); + assert.equal(data.currentParentId, MENU.id, + 'correct currentParentId'); + assert.equal(data.previousIndex, 0, 'correct previousIndex'); + assert.equal(data.currentIndex, 0, 'correct currentIndex'); + + events.off('data', handler); + complete(); + } + events.on('data', handler); + + createBookmarkItem({ + title: 'bookmark-moved-title', + group: UNSORTED + }).then(item => { + id = item.id; + item.group = MENU; + return saveP(item); + }).then(complete); +}; + +exports['test bookmark-item-removed'] = function (assert, done) { + let id; + let complete = makeCompleted(done); + function handler ({type, data}) { + if (type !== 'bookmark-item-removed') return; + if (data.id !== id) return; + assert.equal(type, 'bookmark-item-removed', + 'correct type in bookmark-item-removed event'); + assert.equal(data.type, 'bookmark', + 'correct data in bookmark-item-removed event'); + assert.ok(data.id === id, 'correct id in bookmark-item-removed event'); + assert.equal(data.parentId, UNSORTED.id, + 'correct parentId in bookmark-item-removed'); + assert.equal(data.url, 'http://moz.com/', + 'correct url in bookmark-item-removed event'); + assert.equal(data.index, 0, + 'correct index in bookmark-item-removed event'); + + events.off('data', handler); + complete(); + } + events.on('data', handler); + + createBookmarkItem({ + title: 'bookmark-item-remove-title', + group: UNSORTED + }).then(item => { + id = item.id; + item.remove = true; + return saveP(item); + }).then(complete); +}; + +exports['test bookmark-item-visited'] = function (assert, done) { + let id; + let complete = makeCompleted(done); + function handler ({type, data}) { + if (type !== 'bookmark-item-visited') return; + if (data.id !== id) return; + assert.equal(type, 'bookmark-item-visited', + 'correct type in bookmark-item-visited event'); + assert.ok(data.id === id, 'correct id in bookmark-item-visited event'); + assert.equal(data.parentId, UNSORTED.id, + 'correct parentId in bookmark-item-visited'); + assert.ok(data.transitionType != null, + 'has a transition type in bookmark-item-visited event'); + assert.ok(data.time != null, + 'has a time in bookmark-item-visited event'); + assert.ok(data.visitId != null, + 'has a visitId in bookmark-item-visited event'); + assert.equal(data.url, 'http://bookmark-item-visited.com/', + 'correct url in bookmark-item-visited event'); + + events.off('data', handler); + complete(); + } + events.on('data', handler); + + createBookmarkItem({ + title: 'bookmark-item-visited', + url: 'http://bookmark-item-visited.com/' + }).then(item => { + id = item.id; + return addVisits('http://bookmark-item-visited.com/'); + }).then(complete); +}; + +exports['test history-start-batch, history-end-batch, history-start-clear'] = function (assert, done) { + let complete = makeCompleted(done, 4); + let startEvent = filter(events, ({type}) => type === 'history-start-batch'); + let endEvent = filter(events, ({type}) => type === 'history-end-batch'); + let clearEvent = filter(events, ({type}) => type === 'history-start-clear'); + function startHandler ({type, data}) { + assert.pass('history-start-batch called'); + assert.equal(type, 'history-start-batch', + 'history-start-batch has correct type'); + off(startEvent, 'data', startHandler); + on(endEvent, 'data', endHandler); + complete(); + } + function endHandler ({type, data}) { + assert.pass('history-end-batch called'); + assert.equal(type, 'history-end-batch', + 'history-end-batch has correct type'); + off(endEvent, 'data', endHandler); + complete(); + } + function clearHandler ({type, data}) { + assert.pass('history-start-clear called'); + assert.equal(type, 'history-start-clear', + 'history-start-clear has correct type'); + off(clearEvent, 'data', clearHandler); + complete(); + } + + on(startEvent, 'data', startHandler); + on(clearEvent, 'data', clearHandler); + + createBookmark().then(() => { + resetPlaces(complete); + }) +}; + +exports['test history-visit, history-title-changed'] = function (assert, done) { + let complete = makeCompleted(() => { + off(titleEvents, 'data', titleHandler); + off(visitEvents, 'data', visitHandler); + done(); + }, 6); + let visitEvents = filter(events, ({type}) => type === 'history-visit'); + let titleEvents = filter(events, ({type}) => type === 'history-title-changed'); + + let urls = ['http://moz.com/', 'http://firefox.com/', 'http://mdn.com/']; + + function visitHandler ({type, data}) { + assert.equal(type, 'history-visit', 'correct type in history-visit'); + assert.ok(~urls.indexOf(data.url), 'history-visit has correct url'); + assert.ok(data.visitId != null, 'history-visit has a visitId'); + assert.ok(data.time != null, 'history-visit has a time'); + assert.ok(data.sessionId != null, 'history-visit has a sessionId'); + assert.ok(data.referringId != null, 'history-visit has a referringId'); + assert.ok(data.transitionType != null, 'history-visit has a transitionType'); + complete(); + } + + function titleHandler ({type, data}) { + assert.equal(type, 'history-title-changed', + 'correct type in history-title-changed'); + assert.ok(~urls.indexOf(data.url), + 'history-title-changed has correct url'); + assert.ok(data.title, 'history-title-changed has title'); + complete(); + } + + on(titleEvents, 'data', titleHandler); + on(visitEvents, 'data', visitHandler); + addVisits(urls); +} + +exports['test history-delete-url'] = function (assert, done) { + let complete = makeCompleted(() => { + events.off('data', handler); + done(); + }, 3); + let urls = ['http://moz.com/', 'http://firefox.com/', 'http://mdn.com/']; + function handler({type, data}) { + if (type !== 'history-delete-url') return; + assert.equal(type, 'history-delete-url', + 'history-delete-url has correct type'); + assert.ok(~urls.indexOf(data.url), 'history-delete-url has correct url'); + complete(); + } + + events.on('data', handler); + addVisits(urls).then(() => { + removeVisits(urls); + }); +}; + +exports['test history-page-changed'] = function (assert) { + assert.pass('history-page-changed tested in test-places-favicons'); +}; + +exports['test history-delete-visits'] = function (assert) { + assert.pass('TODO test history-delete-visits'); +}; + +before(exports, (name, assert, done) => resetPlaces(done)); +after(exports, (name, assert, done) => resetPlaces(done)); + +function saveP () { + return promisedEmitter(save.apply(null, Array.slice(arguments))); +} + +function makeCompleted (done, countTo) { + let count = 0; + countTo = countTo || 2; + return function () { + if (++count === countTo) done(); + }; +} +require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-tabs-common.js b/addon-sdk/source/test/test-tabs-common.js index b225b8ef47ab..247788880634 100644 --- a/addon-sdk/source/test/test-tabs-common.js +++ b/addon-sdk/source/test/test-tabs-common.js @@ -457,3 +457,100 @@ exports.testTabReload = function(test) { } }); }; + +exports.testOnPageShowEvent = function (test) { + test.waitUntilDone(); + + let events = []; + let firstUrl = 'data:text/html;charset=utf-8,First'; + let secondUrl = 'data:text/html;charset=utf-8,Second'; + + let counter = 0; + function onPageShow (tab, persisted) { + events.push('pageshow'); + counter++; + if (counter === 1) { + test.assertEqual(persisted, false, 'page should not be cached on initial load'); + tab.url = secondUrl; + } + else if (counter === 2) { + test.assertEqual(persisted, false, 'second test page should not be cached either'); + tab.attach({ + contentScript: 'setTimeout(function () { window.history.back(); }, 0)' + }); + } + else { + test.assertEqual(persisted, true, 'when we get back to the fist page, it has to' + + 'come from cache'); + tabs.removeListener('pageshow', onPageShow); + tabs.removeListener('open', onOpen); + tabs.removeListener('ready', onReady); + tab.close(() => { + ['open', 'ready', 'pageshow', 'ready', + 'pageshow', 'pageshow'].map((type, i) => { + test.assertEqual(type, events[i], 'correct ordering of events'); + }); + test.done() + }); + } + } + + function onOpen () events.push('open'); + function onReady () events.push('ready'); + + tabs.on('pageshow', onPageShow); + tabs.on('open', onOpen); + tabs.on('ready', onReady); + tabs.open({ + url: firstUrl + }); +}; + +exports.testOnPageShowEventDeclarative = function (test) { + test.waitUntilDone(); + + let events = []; + let firstUrl = 'data:text/html;charset=utf-8,First'; + let secondUrl = 'data:text/html;charset=utf-8,Second'; + + let counter = 0; + function onPageShow (tab, persisted) { + events.push('pageshow'); + counter++; + if (counter === 1) { + test.assertEqual(persisted, false, 'page should not be cached on initial load'); + tab.url = secondUrl; + } + else if (counter === 2) { + test.assertEqual(persisted, false, 'second test page should not be cached either'); + tab.attach({ + contentScript: 'setTimeout(function () { window.history.back(); }, 0)' + }); + } + else { + test.assertEqual(persisted, true, 'when we get back to the fist page, it has to' + + 'come from cache'); + tabs.removeListener('pageshow', onPageShow); + tabs.removeListener('open', onOpen); + tabs.removeListener('ready', onReady); + tab.close(() => { + ['open', 'ready', 'pageshow', 'ready', + 'pageshow', 'pageshow'].map((type, i) => { + test.assertEqual(type, events[i], 'correct ordering of events'); + }); + test.done() + }); + } + } + + function onOpen () events.push('open'); + function onReady () events.push('ready'); + + tabs.open({ + url: firstUrl, + onPageShow: onPageShow, + onOpen: onOpen, + onReady: onReady + }); +}; + diff --git a/addon-sdk/source/test/test-url.js b/addon-sdk/source/test/test-url.js index 6f383a37b237..bfd55572a546 100644 --- a/addon-sdk/source/test/test-url.js +++ b/addon-sdk/source/test/test-url.js @@ -3,7 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; -const { URL, toFilename, fromFilename, isValidURI, getTLD, DataURL } = require('sdk/url'); +const { + URL, + toFilename, + fromFilename, + isValidURI, + getTLD, + DataURL, + isLocalURL } = require('sdk/url'); + const { pathFor } = require('sdk/system'); const file = require('sdk/io/file'); const tabs = require('sdk/tabs'); @@ -63,11 +71,11 @@ exports.testParseHttpSearchAndHash = function (assert) { var info = URL('https://www.moz.com/some/page.html'); assert.equal(info.hash, ''); assert.equal(info.search, ''); - + var hashOnly = URL('https://www.sub.moz.com/page.html#justhash'); assert.equal(hashOnly.search, ''); assert.equal(hashOnly.hash, '#justhash'); - + var queryOnly = URL('https://www.sub.moz.com/page.html?my=query'); assert.equal(queryOnly.search, '?my=query'); assert.equal(queryOnly.hash, ''); @@ -75,11 +83,11 @@ exports.testParseHttpSearchAndHash = function (assert) { var qMark = URL('http://www.moz.org?'); assert.equal(qMark.search, ''); assert.equal(qMark.hash, ''); - + var hash = URL('http://www.moz.org#'); assert.equal(hash.search, ''); assert.equal(hash.hash, ''); - + var empty = URL('http://www.moz.org?#'); assert.equal(hash.search, ''); assert.equal(hash.hash, ''); @@ -347,6 +355,39 @@ exports.testWindowLocationMatch = function (assert, done) { }) }; +exports.testURLInRegExpTest = function(assert) { + let url = 'https://mozilla.org'; + assert.equal((new RegExp(url).test(URL(url))), true, 'URL instances work in a RegExp test'); +} + +exports.testLocalURL = function(assert) { + [ + 'data:text/html;charset=utf-8,foo and bar', + 'data:text/plain,foo and bar', + 'resource://gre/modules/commonjs/', + 'chrome://browser/content/browser.xul' + ].forEach(aUri => { + assert.ok(isLocalURL(aUri), aUri + ' is a Local URL'); + }) + +} + +exports.testLocalURLwithRemoteURL = function(assert) { + validURIs().filter(url => !url.startsWith('data:')).forEach(aUri => { + assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); + }); +} + +exports.testLocalURLwithInvalidURL = function(assert) { + invalidURIs().concat([ + 'data:foo and bar', + 'resource:// must fail', + 'chrome:// here too' + ]).forEach(aUri => { + assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); + }); +} + function validURIs() { return [ 'http://foo.com/blah_blah', From 74e8abb3ca3e431f9e3dfa3e4f22ed0404420e95 Mon Sep 17 00:00:00 2001 From: Rodrigo Silveira Date: Wed, 7 Aug 2013 13:11:44 -0700 Subject: [PATCH 09/40] Bug 881995 - Snapped view Alignment for Bookmarks an Recent History is off and tiles are cut off --HG-- extra : rebase_source : c555f8623515499f7cdf84f11e0b29a8c43343cd --- browser/metro/base/content/browser.xul | 3 --- browser/metro/theme/browser.css | 7 ++++--- browser/metro/theme/platform.css | 10 ++++++++++ browser/metro/theme/tiles.css | 9 +++++++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/browser/metro/base/content/browser.xul b/browser/metro/base/content/browser.xul index f06d5f01427f..ad3e852cfcbf 100644 --- a/browser/metro/base/content/browser.xul +++ b/browser/metro/base/content/browser.xul @@ -228,10 +228,7 @@ - - - diff --git a/browser/metro/theme/browser.css b/browser/metro/theme/browser.css index 74fb2efea2e2..521d7cbbe625 100644 --- a/browser/metro/theme/browser.css +++ b/browser/metro/theme/browser.css @@ -234,17 +234,18 @@ documenttab[selected] .documenttab-selection { #start-autocomplete[viewstate="snapped"] .richgrid-item-content { -moz-box-orient: horizontal; } + #start-container, #start-autocomplete { padding-left: 0; padding-right: 0; } + #start-container[viewstate="snapped"] #start-scrollbox > .meta-section { - margin: 0; + margin: 0 @metro_spacing_xnormal@; min-width: @grid_double_column_width@; - -moz-box-flex: 1; - -moz-box-align: center; } + #start-container[viewstate="snapped"] richgrid { visibility: collapse; } diff --git a/browser/metro/theme/platform.css b/browser/metro/theme/platform.css index 6d2104e14b06..660c6c55a517 100644 --- a/browser/metro/theme/platform.css +++ b/browser/metro/theme/platform.css @@ -581,6 +581,16 @@ arrowbox { cursor: default; } +#start-container[viewstate="snapped"] { + padding-top: 0; +} + +#start-container[viewstate="snapped"] .meta-section-title, +#start-container[viewstate="snapped"] richgrid { + margin-top: @metro_spacing_xnormal@; + padding: 0; +} + #start-container[viewstate="snapped"] .meta-section-title.narrow-title, #start-container:not([viewstate="snapped"]) .meta-section-title.wide-title { display: block; diff --git a/browser/metro/theme/tiles.css b/browser/metro/theme/tiles.css index 6480b074de87..22e6b8f8bb61 100644 --- a/browser/metro/theme/tiles.css +++ b/browser/metro/theme/tiles.css @@ -282,4 +282,13 @@ richgriditem[bending] > .tile-content { background: #fff; opacity: 1.0; } + + .tile-content { + left: 0; + right: 0; + } + + richgriditem { + width: auto; + } } From c43fdfc30771c06fc64e7ddeb7c7c186a23e7190 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Thu, 8 Aug 2013 15:10:40 -0700 Subject: [PATCH 10/40] Backing out 9c17e6d4739d due to test failures. --- .../source/doc/dev-guide-source/credits.md | 1 - .../doc/module-source/sdk/indexed-db.md | 185 +++---- .../doc/module-source/sdk/places/bookmarks.md | 450 ------------------ .../doc/module-source/sdk/places/history.md | 110 ----- .../source/doc/module-source/sdk/request.md | 2 +- .../doc/module-source/sdk/simple-prefs.md | 17 - addon-sdk/source/lib/sdk/content/worker.js | 2 +- .../source/lib/sdk/deprecated/api-utils.js | 76 +-- addon-sdk/source/lib/sdk/net/xhr.js | 4 +- addon-sdk/source/lib/sdk/places/events.js | 121 ----- .../lib/sdk/places/host/host-bookmarks.js | 17 +- addon-sdk/source/lib/sdk/places/utils.js | 13 - addon-sdk/source/lib/sdk/tabs/events.js | 9 +- addon-sdk/source/lib/sdk/tabs/tab-fennec.js | 11 - addon-sdk/source/lib/sdk/url.js | 14 +- addon-sdk/source/lib/sdk/util/array.js | 18 +- .../source/lib/sdk/windows/tabs-fennec.js | 12 +- .../source/python-lib/cuddlefish/__init__.py | 6 +- .../cuddlefish/tests/test_linker.py | 7 +- .../source/python-lib/mozrunner/__init__.py | 6 +- .../python-lib/mozrunner/killableprocess.py | 3 +- .../addons/private-browsing-supported/main.js | 15 +- .../private-browsing-supported/test-tabs.js | 82 +++- .../test-window-tabs.js | 85 ---- .../test/addons/privileged-panel/main.js | 16 +- addon-sdk/source/test/favicon-helpers.js | 46 +- addon-sdk/source/test/places-helper.js | 7 - .../source/test/tabs/test-firefox-tabs.js | 34 ++ addon-sdk/source/test/test-api-utils.js | 231 +++------ addon-sdk/source/test/test-array.js | 134 +++--- addon-sdk/source/test/test-browser-events.js | 6 - addon-sdk/source/test/test-content-events.js | 6 +- addon-sdk/source/test/test-path.js | 5 + addon-sdk/source/test/test-places-events.js | 292 ------------ addon-sdk/source/test/test-tabs-common.js | 97 ---- addon-sdk/source/test/test-url.js | 51 +- 36 files changed, 408 insertions(+), 1783 deletions(-) delete mode 100644 addon-sdk/source/doc/module-source/sdk/places/bookmarks.md delete mode 100644 addon-sdk/source/doc/module-source/sdk/places/history.md delete mode 100644 addon-sdk/source/lib/sdk/places/events.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js delete mode 100644 addon-sdk/source/test/test-places-events.js diff --git a/addon-sdk/source/doc/dev-guide-source/credits.md b/addon-sdk/source/doc/dev-guide-source/credits.md index 73de5d5fb304..30386881911b 100644 --- a/addon-sdk/source/doc/dev-guide-source/credits.md +++ b/addon-sdk/source/doc/dev-guide-source/credits.md @@ -140,7 +140,6 @@ We'd like to thank our many Jetpack project contributors! They include: * Tim Taubert * Shane Tomlinson * Dave Townsend -* [Fraser Tweedale](https://github.com/frasertweedale) * [Matthias Tylkowski](https://github.com/tylkomat) ### V ### diff --git a/addon-sdk/source/doc/module-source/sdk/indexed-db.md b/addon-sdk/source/doc/module-source/sdk/indexed-db.md index 260374593ab9..81390e17e275 100644 --- a/addon-sdk/source/doc/module-source/sdk/indexed-db.md +++ b/addon-sdk/source/doc/module-source/sdk/indexed-db.md @@ -32,128 +32,20 @@ So you can use the `indexed-db` module to access the same API: console.log("success"); }; -Most of the objects that implement the IndexedDB API, such as -[IDBTransaction](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBTransaction), -[IDBOpenDBRequest](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBOpenDBRequest), -and [IDBObjectStore](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBObjectStore), -are accessible through the indexedDB object itself. +This module also exports all the other objects that implement +the IndexedDB API, listed below under +[API Reference](modules/sdk/indexed-db.html#API Reference). The API exposed by `indexed-db` is almost identical to the DOM IndexedDB API, so we haven't repeated its documentation here, but refer you to the [IndexedDB API documentation](https://developer.mozilla.org/en-US/docs/IndexedDB) for all the details. -The database created will be unique and private per add-on, and is not linked -to any website database. The module cannot be used to interact with a given -website database. See -[bug 778197](https://bugzilla.mozilla.org/show_bug.cgi?id=779197) and -[bug 786688](https://bugzilla.mozilla.org/show_bug.cgi?id=786688). +The database created will be unique and private per addon, and is not linked to any website database. The module cannot be used to interact with a given website database. See [bug 778197](https://bugzilla.mozilla.org/show_bug.cgi?id=779197) and [bug 786688](https://bugzilla.mozilla.org/show_bug.cgi?id=786688). -## Example +## Example of Usage -Here's a complete add-on that adds two widgets to the browser: the widget labeled -"Add" add the title of the current tab to a database, while the widget labeled -"List" lists all the titles in the database. - -The add-on implements helper functions `open()`, `addItem()` and `getItems()` -to open the database, add a new item to the database, and get all items in the -database. - - var { indexedDB, IDBKeyRange } = require('sdk/indexed-db'); - var widgets = require("sdk/widget"); - - var database = {}; - - database.onerror = function(e) { - console.error(e.value) - } - - function open(version) { - var request = indexedDB.open("stuff", version); - - request.onupgradeneeded = function(e) { - var db = e.target.result; - e.target.transaction.onerror = database.onerror; - - if(db.objectStoreNames.contains("items")) { - db.deleteObjectStore("items"); - } - - var store = db.createObjectStore("items", - {keyPath: "time"}); - }; - - request.onsuccess = function(e) { - database.db = e.target.result; - }; - - request.onerror = database.onerror; - }; - - function addItem(name) { - var db = database.db; - var trans = db.transaction(["items"], "readwrite"); - var store = trans.objectStore("items"); - var time = new Date().getTime(); - var request = store.put({ - "name": name, - "time": time - }); - - request.onerror = database.onerror; - }; - - function getItems(callback) { - var cb = callback; - var db = database.db; - var trans = db.transaction(["items"], "readwrite"); - var store = trans.objectStore("items"); - var items = new Array(); - - trans.oncomplete = function() { - cb(items); - } - - var keyRange = IDBKeyRange.lowerBound(0); - var cursorRequest = store.openCursor(keyRange); - - cursorRequest.onsuccess = function(e) { - var result = e.target.result; - if(!!result == false) - return; - - items.push(result.value.name); - result.continue(); - }; - - cursorRequest.onerror = database.onerror; - }; - - function listItems(itemList) { - console.log(itemList); - } - - open("1"); - - widgets.Widget({ - id: "add-it", - width: 50, - label: "Add", - content: "Add", - onClick: function() { - addItem(require("sdk/tabs").activeTab.title); - } - }); - - widgets.Widget({ - id: "list-them", - width: 50, - label: "List", - content: "List", - onClick: function() { - getItems(listItems); - } - }); +[Promise-based example using indexedDB for record storage](https://github.com/gregglind/micropilot/blob/ec65446d611a65b0646be1806359c463193d5a91/lib/micropilot.js#L80-L198). @property {object} @@ -169,6 +61,71 @@ Defines a range of keys. See the [IDBKeyRange documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBKeyRange). + +@property {object} + +For traversing or iterating records in a database. +See the [IDBCursor documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBCursor). + + + + +@property {object} + +Represents a database transaction. +See the [IDBTransaction documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBTransaction). + + + +@property {object} + +Represents an asynchronous request to open a database. +See the [IDBOpenDBRequest documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBOpenDBRequest). + + + +@property {object} + +Event indicating that the database version has changed. +See the [IDBVersionChangeEvent documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBVersionChangeEvent). + + + +@property {object} + +Represents a connection to a database. +See the [IDBDatabase documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBDatabase). + + + +@property {object} + +Enables you to create, open, and delete databases. +See the [IDBFactory documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBFactory). + + + +@property {object} + +Provides access to a database index. +See the [IDBIndex documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBIndex). + + + +@property {object} + +Represents an object store in a database. +See the [IDBObjectStore documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBObjectStore). + + + +@property {object} + +Provides access to the results of asynchronous requests to databases +and database objects. +See the [IDBRequest documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBRequest). + + @property {object} diff --git a/addon-sdk/source/doc/module-source/sdk/places/bookmarks.md b/addon-sdk/source/doc/module-source/sdk/places/bookmarks.md deleted file mode 100644 index a98a83b5f5c6..000000000000 --- a/addon-sdk/source/doc/module-source/sdk/places/bookmarks.md +++ /dev/null @@ -1,450 +0,0 @@ - - -The `places/bookmarks` module provides functions for creating, modifying and searching bookmark items. It exports: - -* three constructors: [Bookmark](modules/sdk/places/bookmarks.html#Bookmark), [Group](modules/sdk/places/bookmarks.html#Group), and [Separator](modules/sdk/places/bookmarks.html#Separator), corresponding to the types of objects, referred to as **bookmark items**, in the Bookmarks database in Firefox -* two additional functions, [`save()`](modules/sdk/places/bookmarks.html#save(bookmarkItems%2C%20options)) to create, update, and remove bookmark items, and [`search()`](modules/sdk/places/bookmarks.html#search(queries%2C%20options)) to retrieve the bookmark items that match a particular set of criteria. - -`save()` and `search()` are both asynchronous functions: they synchronously return a [`PlacesEmitter`](modules/sdk/places/bookmarks.html#PlacesEmitter) object, which then asynchronously emits events as the operation progresses and completes. - -Each retrieved bookmark item represents only a snapshot of state at a specific time. The module does not automatically sync up a `Bookmark` instance with ongoing changes to that item in the database from the same add-on, other add-ons, or the user. - -## Examples - -### Creating a new bookmark - - let { Bookmark, save } = require("sdk/places/bookmarks"); - - // Create a new bookmark instance, unsaved - let bookmark = Bookmark({ title: "Mozilla", url: "http://mozila.org" }); - - // Attempt to save the bookmark instance to the Bookmarks database - // and store the emitter - let emitter = save(bookmark); - - // Listen for events - emitter.on("data", function (saved, inputItem) { - // on a "data" event, an item has been updated, passing in the - // latest snapshot from the server as `saved` (with properties - // such as `updated` and `id`), as well as the initial input - // item as `inputItem` - console.log(saved.title === inputItem.title); // true - console.log(saved !== inputItem); // true - console.log(inputItem === bookmark); // true - }).on("end", function (savedItems, inputItems) { - // Similar to "data" events, except "end" is an aggregate of - // all progress events, with ordered arrays as `savedItems` - // and `inputItems` - }); - -### Creating several bookmarks with a new group - - let { Bookmark, Group, save } = require("sdk/places/bookmarks"); - - let group = Group({ title: "Guitars" }); - let bookmarks = [ - Bookmark({ title: "Ran", url: "http://ranguitars.com", group: group }), - Bookmark({ title: "Ibanez", url: "http://ibanez.com", group: group }), - Bookmark({ title: "ESP", url: "http://espguitars.com", group: group }) - ]; - - // Save `bookmarks` array -- notice we don't have `group` in the array, - // although it needs to be saved since all bookmarks are children - // of `group`. This will be saved implicitly. - - save(bookmarks).on("data", function (saved, input) { - // A data event is called once for each item saved, as well - // as implicit items, like `group` - console.log(input === group || ~bookmarks.indexOf(input)); // true - }).on("end", function (saves, inputs) { - // like the previous example, the "end" event returns an - // array of all of our updated saves. Only explicitly saved - // items are returned in this array -- the `group` won't be - // present here. - console.log(saves[0].title); // "Ran" - console.log(saves[2].group.title); // "Guitars" - }); - -### Searching for bookmarks - -Bookmarks can be queried with the [`search()`](modules/sdk/places/bookmarks.html#search(queries%2C%20options)) function, which accepts a query object or an array of query objects, as well as a query options object. Query properties are AND'd together within a single query object, but are OR'd together across multiple query objects. - - let { search, UNSORTED } = require("sdk/places/bookmarks"); - - // Simple query with one object - search( - { query: "firefox" }, - { sort: "title" } - ).on(end, function (results) { - // results matching any bookmark that has "firefox" - // in its URL, title or tag, sorted by title - }); - - // Multiple queries are OR'd together - search( - [{ query: "firefox" }, { group: UNSORTED, tags: ["mozilla"] }], - { sort: "title" } - ).on("end", function (results) { - // Our first query is the same as the simple query above; - // all of those results are also returned here. Since multiple - // queries are OR'd together, we also get bookmarks that - // match the second query. The second query's properties - // are AND'd together, so results that are in the platform's unsorted - // bookmarks folder, AND are also tagged with 'mozilla', get returned - // as well in this query - }); - - -@class - -@constructor - -Creates an unsaved bookmark instance. -@param options {object} - Options for the bookmark, with the following parameters: - @prop title {string} - The title for the bookmark. Required. - @prop url {string} - The URL for the bookmark. Required. - @prop [group] {Group} - The parent group that the bookmark lives under. Defaults to the [Bookmarks.UNSORTED](modules/sdk/places/bookmarks.html#UNSORTED) group. - @prop [index] {number} - The index of the bookmark within its group. Last item within the group by default. - @prop [tags] {set} - A set of tags to be applied to the bookmark. - - - -@property {string} - The bookmark's title. - - - -@property {string} - The bookmark's URL. - - - -@property {Group} - The group instance that the bookmark lives under. - - - -@property {number} - The index of the bookmark within its group. - - - -@property {number} - A Unix timestamp indicating when the bookmark was last updated on the platform. - - - -@property {set} - A [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) of tags that the bookmark is tagged with. - - - - -@class - -@constructor - -Creates an unsaved bookmark group instance. -@param options {object} - Options for the bookmark group, with the following parameters: - @prop title {string} - The title for the group. Required. - @prop [group] {Group} - The parent group that the bookmark group lives under. Defaults to the [Bookmarks.UNSORTED](modules/sdk/places/bookmarks.html#UNSORTED) group. - @prop [index] {number} - The index of the bookmark group within its parent group. Last item within the group by default. - - - -@property {string} - The bookmark group's title. - - - -@property {Group} - The group instance that the bookmark group lives under. - - - -@property {number} - The index of the bookmark group within its group. - - - -@property {number} - A Unix timestamp indicating when the bookmark was last updated on the platform. - - - - -@class - -@constructor - -Creates an unsaved bookmark separator instance. -@param options {object} - Options for the bookmark group, with the following parameters: - @prop [group] {Group} - The parent group that the bookmark group lives under. Defaults to the [Bookmarks.UNSORTED](modules/sdk/places/bookmarks.html#UNSORTED) group. - @prop [index] {number} - The index of the bookmark group within its parent group. Last item within the group by default. - - - -@property {Group} - The group instance that the bookmark group lives under. - - - -@property {number} - The index of the bookmark group within its group. - - - -@property {number} - A Unix timestamp indicating when the bookmark was last updated on the platform. - - - - -@function - -Creating, saving, and deleting bookmarks are all done with the `save()` function. This function takes in any of: - -* a bookmark item (Bookmark, Group, Separator) -* a duck-typed object (the relative properties for a bookmark item, in addition to a `type` property of `'bookmark'`, `'group'`, or `'separator'`) -* an array of bookmark items. - -All of the items passed in are pushed to the platform and are either created, updated or deleted. - -* adding items: if passing in freshly instantiated bookmark items or a duck-typed object, the item is created on the platform. -* updating items: for an item referenced from a previous `save()` or from the result of a `search()` query, changing the properties and calling `save()` will update the item on the server. -* deleting items: to delete a bookmark item, pass in a bookmark item with a property `remove` set to `true`. - -The function returns a [`PlacesEmitter`](modules/sdk/places/bookmarks.html#PlacesEmitter) that emits a `data` event for each item as it is saved, and an `end` event when all items have been saved. - - let { Bookmark, Group } = require("sdk/places/bookmarks"); - - let myGroup = Group({ title: "My Group" }); - let myBookmark = Bookmark({ - title: "Moz", - url: "http://mozilla.com", - group: myGroup - }); - - save(myBookmark).on("data", function (item, inputItem) { - // The `data` event returns the latest snapshot from the - // host, so this is a new instance of the bookmark item, - // where `item !== myBookmark`. To match it with the input item, - // use the second argument, so `inputItem === myBookmark` - - // All explicitly saved items have data events called, as - // well as implicitly saved items. In this case, - // `myGroup` has to be saved before `myBookmark`, since - // `myBookmark` is a child of `myGroup`. `myGroup` will - // also have a `data` event called for it. - }).on("end", function (items, inputItems) { - // The `end` event returns all items that are explicitly - // saved. So `myGroup` will not be in this array, - // but `myBookmark` will be. - // `inputItems` matches the initial input as an array, - // so `inputItems[0] === myBookmark` - }); - - // Saving multiple bookmarks, as duck-types in this case - - let bookmarks = [ - { title: "mozilla", url: "http://mozilla.org", type: "bookmark" }, - { title: "firefox", url: "http://firefox.com", type: "bookmark" }, - { title: "twitter", url: "http://twitter.com", type: "bookmark" } - ]; - - save(bookmarks).on("data", function (item, inputItem) { - // Each item in `bookmarks` has its own `data` event - }).on("end", function (results, inputResults) { - // `results` is an array of items saved in the same order - // as they were passed in. - }); - -@param bookmarkItems {bookmark|group|separator|array} - A bookmark item ([Bookmark](modules/sdk/places/bookmarks.html#Bookmark), [Group](modules/sdk/places/bookmarks.html#Group), [Separator](modules/sdk/places/bookmarks.html#Separator)), or an array of bookmark items to be saved. - -@param [options] {object} - An optional options object that takes the following properties: - @prop [resolve] {function} - A resolution function that is invoked during an attempt to save - a bookmark item that is not derived from the latest state from - the platform. Invoked with two arguments, `mine` and `platform`, where - `mine` is the item that is being saved, and `platform` is the - current state of the item on the item. The object returned from - this function is what is saved on the platform. By default, all changes - on an outdated bookmark item overwrite the platform's bookmark item. - -@returns {PlacesEmitter} - Returns a [PlacesEmitter](modules/sdk/places/bookmarks.html#PlacesEmitter). - - - -@function - -A helper function that takes in a bookmark item, or an `Array` of several bookmark items, and sets each item's `remove` property to true. This does not remove the bookmark item from the database: it must be subsequently saved. - - let { search, save, remove } = require("sdk/places/bookmarks"); - - search({ tags: ["php"] }).on("end", function (results) { - // The search returns us all bookmark items that are - // tagged with `"php"`. - - // We then pass `results` into the remove function to mark - // all items to be removed, which returns the new modified `Array` - // of items, which is passed into save. - save(remove(results)).on("end", function (results) { - // items tagged with `"php"` are now removed! - }); - }) - -@param items {Bookmark|Group|Separator|array} - A bookmark item, or `Array` of bookmark items to be transformed to set their `remove` property to `true`. - -@returns {array} - An array of the transformed bookmark items. - - - -@function - -Queries can be performed on bookmark items by passing in one or more query objects, each of which is given one or more properties. - -Within each query object, the properties are AND'd together: so only objects matching all properties are retrieved. Across query objects, the results are OR'd together, meaning that if an item matches any of the query objects, it will be retrieved. - -For example, suppose we called `search()` with two query objects: - -
[{ url: "mozilla.org", tags: ["mobile"]},
- { tags: ["firefox-os"]}]
- -This will return: - -* all bookmark items from mozilla.org that are also tagged "mobile" -* all bookmark items that are tagged "firefox-os" - -An `options` object may be used to determine overall settings such as sort order and how many objects should be returned. - -@param queries {object|array} -An `Object` representing a query, or an `Array` of `Objects` representing queries. Each query object can take several properties, which are queried against the bookmarks database. Each property is AND'd together, meaning that bookmarks must match each property within a query object. Multiple query objects are then OR'd together. - @prop [group] {Group} - Group instance that should be owners of the returned children bookmarks. If no `group` specified, all bookmarks are under the search space. - @prop [tags] {set|array} - Bookmarks with corresponding tags. These are AND'd together. - @prop [url] {string} - A string that matches bookmarks' URL. The following patterns are accepted: - - `'*.mozilla.com'`: matches any URL with 'mozilla.com' as the host, accepting any subhost. - - `'mozilla.com'`: matches any URL with 'mozilla.com' as the host. - - `'http://mozilla.com'`: matches 'http://mozilla.com' exactly. - - `'http://mozilla.com/*'`: matches any URL that starts with 'http://mozilla.com/'. - @prop [query] {string} - A string that matches bookmarks' URL, title and tags. - -@param [options] {object} -An `Object` with options for the search query. - @prop [count] {number} - The number of bookmark items to return. If left undefined, no limit is set. - @prop [sort] {string} - A string specifying how the results should be sorted. Possible options are `'title'`, `'date'`, `'url'`, `'visitCount'`, `'dateAdded'` and `'lastModified'`. - @prop [descending] {boolean} - A boolean specifying whether the results should be in descending order. By default, results are in ascending order. - -
- - -@class - -The `PlacesEmitter` is not exported from the module, but returned from the `save` and `search` functions. The `PlacesEmitter` inherits from [`event/target`](modules/sdk/event/target.html), and emits `data`, `error`, and `end`. - -`data` events are emitted for every individual operation (such as: each item saved, or each item found by a search query), whereas `end` events are emitted as the aggregate of an operation, passing an array of objects into the handler. - - -@event -The `data` event is emitted when a bookmark item that was passed into the `save` method has been saved to the platform. This includes implicit saves that are dependencies of the explicit items saved. For example, when creating a new bookmark group with two bookmark items as its children, and explicitly saving the two bookmark children, the unsaved parent group will also emit a `data` event. - - let { Bookmark, Group, save } = require("sdk/places/bookmarks"); - - let group = Group({ title: "my group" }); - let bookmarks = [ - Bookmark({ title: "mozilla", url: "http://mozilla.com", group: group }), - Bookmark({ title: "w3", url: "http://w3.org", group: group }) - ]; - - save(bookmarks).on("data", function (item) { - // This function will be called three times: - // once for each bookmark saved - // once for the new group specified implicitly - // as the parent of the two items - }); - -The `data` event is also called for `search` requests, with each result being passed individually into its own `data` event. - - let { search } = require("sdk/places/bookmarks"); - - search({ query: "firefox" }).on("data", function (item) { - // each bookmark item that matches the query will - // be called in this function - }); - -@argument {Bookmark|Group|Separator} - For the `save` function, this is the saved, latest snapshot of the bookmark item. For `search`, this is a snapshot of a bookmark returned from the search query. - -@argument {Bookmark|Group|Separator|object} - Only in `save` data events. The initial instance of the item that was used for the save request. - - - -@event -The `error` event is emitted whenever a bookmark item's save could not be completed. - -@argument {string} - A string indicating the error that occurred. - -@argument {Bookmark|Group|Separator|object} - Only in `save` error events. The initial instance of the item that was used for the save request. - - - -@event -The `end` event is called when all bookmark items and dependencies -have been saved, or an aggregate of all items returned from a search query. - -@argument {array} - The array is an ordered list of the input bookmark items, replaced - with their updated, latest snapshot instances (the first argument - in the `data` handler), or in the case of an error, the initial instance - of the item that was used for the save request - (the second argument in the `data` or `error` handler). - - - - -@property {group} -This is a constant, default [`Group`](modules/sdk/places/bookmarks.html#Group) on the Firefox platform, the **Bookmarks Menu**. It can be used in queries or specifying the parent of a bookmark item, but it cannot be modified. - - - -@property {group} -This is a constant, default [`Group`](modules/sdk/places/bookmarks.html#Group) on the Firefox platform, the **Bookmarks Toolbar**. It can be used in queries or specifying the parent of a bookmark item, but it cannot be modified. - - - -@property {group} -This is a constant, default [`Group`](modules/sdk/places/bookmarks.html#Group) on the Firefox platform, the **Unsorted Bookmarks** group. It can be used in queries or specifying the parent of a bookmark item, but it cannot be modified. - diff --git a/addon-sdk/source/doc/module-source/sdk/places/history.md b/addon-sdk/source/doc/module-source/sdk/places/history.md deleted file mode 100644 index fe3ed144588b..000000000000 --- a/addon-sdk/source/doc/module-source/sdk/places/history.md +++ /dev/null @@ -1,110 +0,0 @@ - - -The `places/history` module provides a single function, [`search()`](modules/sdk/places/history.html#search(queries%2C%20options)), for querying the user's browsing history. - -It synchronously returns a [`PlacesEmitter`](modules/sdk/places/history.html#PlacesEmitter) object which then asynchronously emits [`data`](modules/sdk/places/history.html#data) and [`end`](modules/sdk/places/history.html#end) or [`error`](modules/sdk/places/history.html#error) events that contain information about the state of the operation. - -## Example - - let { search } = require("sdk/places/history"); - - // Simple query - search( - { url: "https://developers.mozilla.org/*" }, - { sort: "visitCount" } - ).on("end", function (results) { - // results is an array of objects containing - // data about visits to any site on developers.mozilla.org - // ordered by visit count - }); - - // Complex query - // The query objects are OR'd together - // Let's say we want to retrieve all visits from before a week ago - // with the query of 'ruby', but from last week onwards, we want - // all results with 'javascript' in the URL or title. - // We'd compose the query with the following options - let lastWeek = Date.now - (1000*60*60*24*7); - search( - // First query looks for all entries before last week with 'ruby' - [{ query: "ruby", to: lastWeek }, - // Second query searches all entries after last week with 'javascript' - { query: "javascript", from: lastWeek }], - // We want to order chronologically by visit date - { sort: "date" } - ).on("end", function (results) { - // results is an array of objects containing visit data, - // sorted by visit date, with all entries from more than a week ago - // that contain 'ruby', *in addition to* entries from this last week - // that contain 'javascript' - }); - - -@function - -Queries can be performed on history entries by passing in one or more query options. Each query option can take several properties, which are **AND**'d together to make one complete query. For additional queries within the query, passing more query options in will **OR** the total results. An `options` object may be specified to determine overall settings, like sorting and how many objects should be returned. - -@param queries {object|array} -An `Object` representing a query, or an `Array` of `Objects` representing queries. Each query object can take several properties, which are queried against the history database. Each property is **AND**'d together, meaning that bookmarks must match each property within a query object. Multiple query objects are then **OR**'d together. - @prop [url] {string} - A string that matches bookmarks' URL. The following patterns are accepted: - - `'*.mozilla.com'`: matches any URL with 'mozilla.com' as the host, accepting any subhost. - - `'mozilla.com'`: matches any URL with 'mozilla.com' as the host. - - `'http://mozilla.com'`: matches 'http://mozilla.com' directlry. - - `'http://mozilla.com/*'`: matches any URL that starts with 'http://mozilla.com/'. - @prop [query] {string} - A string that matches bookmarks' URL, or title. - @prop [from] {number|date} - Time relative from the [Unix epoch](http://en.wikipedia.org/wiki/Unix_time) that history results should be limited to occuring after. Can accept a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object, or milliseconds from the epoch. Default is to return all items since the epoch (all time). - @prop [to] {number|date} - Time relative from the [Unix epoch](http://en.wikipedia.org/wiki/Unix_time) that history results should be limited to occuring before. Can accept a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object, or milliseconds from the epoch. Default is the current time. - -@param [options] {object} -An `Object` with options for the search query. - @prop [count] {number} - The number of bookmark items to return. If left undefined, no limit is set. - @prop [sort] {string} - A string specifying how the results should be sorted. Possible options are `'title'`, `'date'`, `'url'`, `'visitCount'`, `'keyword'`, `'dateAdded'` and `'lastModified'`. - @prop [descending] {boolean} - A boolean specifying whether the results should be in descending order. By default, results are in ascending order. - - - - - -@class - -The `PlacesEmitter` is not exposed in the module, but returned from the `search` functions. The `PlacesEmitter` inherits from [`event/target`](modules/sdk/event/target.html), and emits `data`, `error`, and `end`. `data` events are emitted for every individual search result found, whereas `end` events are emitted as an aggregate of an entire search, passing in an array of all results into the handler. - - -@event -The `data` event is emitted for every item returned from a search. - -@argument {Object} - This is an object representing a history entry. Contains `url`, `time`, `accessCount` and `title` of the entry. - - - -@event -The `error` event is emitted whenever a search could not be completed. - -@argument {string} - A string indicating the error that occurred. - - - -@event -The `end` event is called when all search results have returned. - -@argument {array} - The value passed into the handler is an array of all entries found in the - history search. Each entry is an object containing the properties - `url`, `time`, `accessCount` and `title`. - - diff --git a/addon-sdk/source/doc/module-source/sdk/request.md b/addon-sdk/source/doc/module-source/sdk/request.md index a249ddb85ac8..3f87aefb3610 100644 --- a/addon-sdk/source/doc/module-source/sdk/request.md +++ b/addon-sdk/source/doc/module-source/sdk/request.md @@ -2,7 +2,7 @@ - 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/. --> -The `request` module lets you make simple yet powerful network requests. For more advanced usage, check out the [net/xhr](modules/sdk/net/xhr.html) module, based on the browser's [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object. +The `request` module lets you make simple yet powerful network requests. @class diff --git a/addon-sdk/source/doc/module-source/sdk/simple-prefs.md b/addon-sdk/source/doc/module-source/sdk/simple-prefs.md index 09e2449e6350..ecabb6a7e17e 100644 --- a/addon-sdk/source/doc/module-source/sdk/simple-prefs.md +++ b/addon-sdk/source/doc/module-source/sdk/simple-prefs.md @@ -100,23 +100,6 @@ These are attributes that all settings *may* have: this may be an integer, string, or boolean value. - - hidden -

A boolean value which, if present and set to true, - means that the setting won't appear in the Add-ons Manager interface, - so users of your add-on won't be able to see or alter it.

-
-{
-    "name": "myHiddenInteger",
-    "type": "integer",
-    "title": "How Many?",
-    "hidden": true
-}
-

Your add-on's code will still be able to access and modify it, - just like any other preference you define.

- - - ### Setting-Specific Attributes ### diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js index 2eddaee035de..f8742db0affe 100644 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ b/addon-sdk/source/lib/sdk/content/worker.js @@ -369,7 +369,7 @@ const WorkerSandbox = EventEmitter.compose({ /** * Message-passing facility for communication between code running * in the content and add-on process. - * @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html + * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker */ const Worker = EventEmitter.compose({ on: Trait.required, diff --git a/addon-sdk/source/lib/sdk/deprecated/api-utils.js b/addon-sdk/source/lib/sdk/deprecated/api-utils.js index d4dc835eed95..568042b734e2 100644 --- a/addon-sdk/source/lib/sdk/deprecated/api-utils.js +++ b/addon-sdk/source/lib/sdk/deprecated/api-utils.js @@ -5,16 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; + module.metadata = { "stability": "deprecated" }; const memory = require("./memory"); - -const { merge } = require("../util/object"); -const { union } = require("../util/array"); -const { isNil } = require("../lang/type"); - // The possible return values of getTypeOf. const VALID_TYPES = [ "array", @@ -27,8 +23,6 @@ const VALID_TYPES = [ "undefined", ]; -const { isArray } = Array; - /** * Returns a function C that creates instances of privateCtor. C may be called * with or without the new keyword. The prototype of each instance returned @@ -92,7 +86,6 @@ exports.validateOptions = function validateOptions(options, requirements) { let validatedOptions = {}; for (let key in requirements) { - let isOptional = false; let mapThrew = false; let req = requirements[key]; let [optsVal, keyInOpts] = (key in options) ? @@ -110,27 +103,17 @@ exports.validateOptions = function validateOptions(options, requirements) { } } if (req.is) { - let types = req.is; - - if (!isArray(types) && isArray(types.is)) - types = types.is; - - if (isArray(types)) { - isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v)); - - // Sanity check the caller's type names. - types.forEach(function (typ) { - if (VALID_TYPES.indexOf(typ) < 0) { - let msg = 'Internal error: invalid requirement type "' + typ + '".'; - throw new Error(msg); - } - }); - if (types.indexOf(getTypeOf(optsVal)) < 0) - throw new RequirementError(key, req); - } + // Sanity check the caller's type names. + req.is.forEach(function (typ) { + if (VALID_TYPES.indexOf(typ) < 0) { + let msg = 'Internal error: invalid requirement type "' + typ + '".'; + throw new Error(msg); + } + }); + if (req.is.indexOf(getTypeOf(optsVal)) < 0) + throw new RequirementError(key, req); } - - if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal))) + if (req.ok && !req.ok(optsVal)) throw new RequirementError(key, req); if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined)) @@ -159,7 +142,7 @@ let getTypeOf = exports.getTypeOf = function getTypeOf(val) { if (typ === "object") { if (!val) return "null"; - if (isArray(val)) + if (Array.isArray(val)) return "array"; } return typ; @@ -181,38 +164,3 @@ function RequirementError(key, requirement) { this.message = msg; } RequirementError.prototype = Object.create(Error.prototype); - -let string = { is: ['string', 'undefined', 'null'] }; -exports.string = string; - -let number = { is: ['number', 'undefined', 'null'] }; -exports.number = number; - -let boolean = { is: ['boolean', 'undefined', 'null'] }; -exports.boolean = boolean; - -let object = { is: ['object', 'undefined', 'null'] }; -exports.object = object; - -let isTruthyType = type => !(type === 'undefined' || type === 'null'); -let findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v }; - -function required(req) { - let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType); - - return merge({}, req, {is: types}); -} -exports.required = required; - -function optional(req) { - req = merge({is: []}, req); - req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null'); - - return req; -} -exports.optional = optional; - -function either(...types) { - return union.apply(null, types.map(findTypes)); -} -exports.either = either; diff --git a/addon-sdk/source/lib/sdk/net/xhr.js b/addon-sdk/source/lib/sdk/net/xhr.js index 415b9cbf44de..f9c7c05217c0 100644 --- a/addon-sdk/source/lib/sdk/net/xhr.js +++ b/addon-sdk/source/lib/sdk/net/xhr.js @@ -4,7 +4,7 @@ "use strict"; module.metadata = { - "stability": "stable" + "stability": "unstable" }; const { deprecateFunction } = require("../util/deprecate"); @@ -33,4 +33,4 @@ function forceAllowThirdPartyCookie(xhr) { exports.forceAllowThirdPartyCookie = forceAllowThirdPartyCookie; // No need to handle add-on unloads as addon/window is closed at unload -// and it will take down all the associated requests. +// and it will take down all the associated requests. \ No newline at end of file diff --git a/addon-sdk/source/lib/sdk/places/events.js b/addon-sdk/source/lib/sdk/places/events.js deleted file mode 100644 index 4703d2a65277..000000000000 --- a/addon-sdk/source/lib/sdk/places/events.js +++ /dev/null @@ -1,121 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -module.metadata = { - 'stability': 'experimental', - 'engines': { - 'Firefox': '*' - } -}; - -const { Cc, Ci } = require('chrome'); -const { Unknown } = require('../platform/xpcom'); -const { Class } = require('../core/heritage'); -const { merge } = require('../util/object'); -const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Ci.nsINavBookmarksService); -const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] - .getService(Ci.nsINavHistoryService); -const { mapBookmarkItemType } = require('./utils'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); - -const emitter = EventTarget(); - -let HISTORY_ARGS = { - onBeginUpdateBatch: [], - onEndUpdateBatch: [], - onClearHistory: [], - onDeleteURI: ['url'], - onDeleteVisits: ['url', 'visitTime'], - onPageChanged: ['url', 'property', 'value'], - onTitleChanged: ['url', 'title'], - onVisit: [ - 'url', 'visitId', 'time', 'sessionId', 'referringId', 'transitionType' - ] -}; - -let HISTORY_EVENTS = { - onBeginUpdateBatch: 'history-start-batch', - onEndUpdateBatch: 'history-end-batch', - onClearHistory: 'history-start-clear', - onDeleteURI: 'history-delete-url', - onDeleteVisits: 'history-delete-visits', - onPageChanged: 'history-page-changed', - onTitleChanged: 'history-title-changed', - onVisit: 'history-visit' -}; - -let BOOKMARK_ARGS = { - onItemAdded: [ - 'id', 'parentId', 'index', 'type', 'url', 'title', 'dateAdded' - ], - onItemChanged: [ - 'id', 'property', null, 'value', 'lastModified', 'type', 'parentId' - ], - onItemMoved: [ - 'id', 'previousParentId', 'previousIndex', 'currentParentId', - 'currentIndex', 'type' - ], - onItemRemoved: ['id', 'parentId', 'index', 'type', 'url'], - onItemVisited: ['id', 'visitId', 'time', 'transitionType', 'url', 'parentId'] -}; - -let BOOKMARK_EVENTS = { - onItemAdded: 'bookmark-item-added', - onItemChanged: 'bookmark-item-changed', - onItemMoved: 'bookmark-item-moved', - onItemRemoved: 'bookmark-item-removed', - onItemVisited: 'bookmark-item-visited', -}; - -function createHandler (type, propNames) { - propNames = propNames || []; - return function (...args) { - let data = propNames.reduce((acc, prop, i) => { - if (prop) - acc[prop] = formatValue(prop, args[i]); - return acc; - }, {}); - - emit(emitter, 'data', { - type: type, - data: data - }); - }; -} - -/* - * Creates an observer, creating handlers based off of - * the `events` names, and ordering arguments from `propNames` hash - */ -function createObserverInstance (events, propNames) { - let definition = Object.keys(events).reduce((prototype, eventName) => { - prototype[eventName] = createHandler(events[eventName], propNames[eventName]); - return prototype; - }, {}); - - return Class(merge(definition, { extends: Unknown }))(); -} - -/* - * Formats `data` based off of the value of `type` - */ -function formatValue (type, data) { - if (type === 'type') - return mapBookmarkItemType(data); - if (type === 'url' && data) - return data.spec; - return data; -} - -let historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS); -historyService.addObserver(historyObserver, false); - -let bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS); -bookmarkService.addObserver(bookmarkObserver, false); - -exports.events = emitter; diff --git a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js index acac020231d5..f138bb22e8db 100644 --- a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js +++ b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js @@ -104,22 +104,17 @@ function saveBookmarkItem (data) { let group = bmsrv.getFolderIdForItem(id); let index = bmsrv.getItemIndex(id); let type = bmsrv.getItemType(id); - let title = typeMap(type) !== 'separator' ? - bmsrv.getItemTitle(id) : - undefined; - let url = typeMap(type) === 'bookmark' ? - bmsrv.getBookmarkURI(id).spec : - undefined; - if (url != data.url) + if (data.url) { bmsrv.changeBookmarkURI(id, newURI(data.url)); + } else if (typeMap(type) === 'bookmark') - data.url = url; + data.url = bmsrv.getBookmarkURI(id).spec; - if (title != data.title) + if (data.title) bmsrv.setItemTitle(id, data.title); else if (typeMap(type) !== 'separator') - data.title = title; + data.title = bmsrv.getItemTitle(id); if (data.group && data.group !== group) bmsrv.moveItem(id, data.group, data.index || -1); @@ -128,7 +123,7 @@ function saveBookmarkItem (data) { // so we don't have to manage the indicies of the siblings bmsrv.moveItem(id, group, data.index); } else if (data.index == null) - data.index = index; + data.index = bmsrv.getItemIndex(id); data.updated = bmsrv.getItemLastModified(data.id); diff --git a/addon-sdk/source/lib/sdk/places/utils.js b/addon-sdk/source/lib/sdk/places/utils.js index 306902fb472e..1ce87d717fa0 100644 --- a/addon-sdk/source/lib/sdk/places/utils.js +++ b/addon-sdk/source/lib/sdk/places/utils.js @@ -235,16 +235,3 @@ function createQueryOptions (type, options) { } exports.createQueryOptions = createQueryOptions; - -function mapBookmarkItemType (type) { - if (typeof type === 'number') { - if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; - if (bmsrv.TYPE_FOLDER === type) return 'group'; - if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; - } else { - if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; - if ('group' === type) return bmsrv.TYPE_FOLDER; - if ('separator' === type) return bmsrv.TYPE_SEPARATOR; - } -} -exports.mapBookmarkItemType = mapBookmarkItemType; diff --git a/addon-sdk/source/lib/sdk/tabs/events.js b/addon-sdk/source/lib/sdk/tabs/events.js index 65650f9dcf71..118231b67150 100644 --- a/addon-sdk/source/lib/sdk/tabs/events.js +++ b/addon-sdk/source/lib/sdk/tabs/events.js @@ -26,14 +26,7 @@ exports.EVENTS = EVENTS; Object.keys(EVENTS).forEach(function(name) { EVENTS[name] = { name: name, - listener: createListenerName(name), + listener: ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1), dom: EVENTS[name] } }); - -function createListenerName (name) { - if (name === 'pageshow') - return 'onPageShow'; - else - return ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1); -} diff --git a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js index 70a7659beb68..65929c184c19 100644 --- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js +++ b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js @@ -33,9 +33,6 @@ const Tab = Class({ // TabReady let onReady = tabInternals.onReady = onTabReady.bind(this); tab.browser.addEventListener(EVENTS.ready.dom, onReady, false); - - let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this); - tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow, false); // TabClose let onClose = tabInternals.onClose = onTabClose.bind(this); @@ -183,10 +180,8 @@ function cleanupTab(tab) { if (tabInternals.tab.browser) { tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false); - tabInternals.tab.browser.removeEventListener(EVENTS.pageshow.dom, tabInternals.onPageShow, false); } tabInternals.onReady = null; - tabInternals.onPageShow = null; tabInternals.window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, tabInternals.onClose, false); tabInternals.onClose = null; rawTabNS(tabInternals.tab).tab = null; @@ -203,12 +198,6 @@ function onTabReady(event) { } } -function onTabPageShow(event) { - let win = event.target.defaultView; - if (win === win.top) - emit(this, 'pageshow', this, event.persisted); -} - // TabClose function onTabClose(event) { let rawTab = getTabForBrowser(event.target); diff --git a/addon-sdk/source/lib/sdk/url.js b/addon-sdk/source/lib/sdk/url.js index 06a383ca18f6..87bca3686974 100644 --- a/addon-sdk/source/lib/sdk/url.js +++ b/addon-sdk/source/lib/sdk/url.js @@ -1,6 +1,7 @@ /* 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"; module.metadata = { @@ -276,16 +277,3 @@ let isValidURI = exports.isValidURI = function (uri) { } return true; } - -function isLocalURL(url) { - if (String.indexOf(url, './') === 0) - return true; - - try { - return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1; - } - catch(e) {} - - return false; -} -exports.isLocalURL = isLocalURL; diff --git a/addon-sdk/source/lib/sdk/util/array.js b/addon-sdk/source/lib/sdk/util/array.js index 198024624c7f..75ccf9e8630f 100644 --- a/addon-sdk/source/lib/sdk/util/array.js +++ b/addon-sdk/source/lib/sdk/util/array.js @@ -72,22 +72,12 @@ exports.remove = function remove(array, element) { * Source array. * @returns {Array} */ -function unique(array) { - return array.reduce(function(result, item) { - add(result, item); - return result; +exports.unique = function unique(array) { + return array.reduce(function(values, element) { + add(values, element); + return values; }, []); }; -exports.unique = unique; - -/** - * Produce an array that contains the union: each distinct element from all - * of the passed-in arrays. - */ -function union() { - return unique(Array.concat.apply(null, arguments)); -}; -exports.union = union; exports.flatten = function flatten(array){ var flat = []; diff --git a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js index f5eca6f29740..ba6793d274f8 100644 --- a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js +++ b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js @@ -77,9 +77,6 @@ const Tabs = Class({ if (options.onReady) tab.on('ready', options.onReady); - if (options.onPageShow) - tab.on('pageshow', options.onPageShow); - if (options.onActivate) tab.on('activate', options.onActivate); @@ -134,12 +131,9 @@ function onTabOpen(event) { tab.on('ready', function() emit(gTabs, 'ready', tab)); tab.once('close', onTabClose); - tab.on('pageshow', function(_tab, persisted) - emit(gTabs, 'pageshow', tab, persisted)); - emit(tab, 'open', tab); emit(gTabs, 'open', tab); -} +}; // TabSelect function onTabSelect(event) { @@ -159,10 +153,10 @@ function onTabSelect(event) { emit(t, 'deactivate', t); emit(gTabs, 'deactivate', t); } -} +}; // TabClose function onTabClose(tab) { removeTab(tab); emit(gTabs, EVENTS.close.name, tab); -} +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/__init__.py b/addon-sdk/source/python-lib/cuddlefish/__init__.py index d3826e2fd461..2672da9acb69 100644 --- a/addon-sdk/source/python-lib/cuddlefish/__init__.py +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py @@ -172,7 +172,7 @@ parser_groups = ( (("", "--strip-sdk",), dict(dest="bundle_sdk", help=("Do not ship SDK modules in the xpi"), action="store_false", - default=False, + default=True, cmds=['run', 'test', 'testex', 'testpkgs', 'testall', 'xpi'])), (("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk", @@ -564,7 +564,7 @@ def initializer(env_root, args, out=sys.stdout, err=sys.stderr): print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' else: print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.' - print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' + print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' return {"result":0, "jid":jid} def buildJID(target_cfg): @@ -593,7 +593,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root); - + # reparse configs with arguments from local.json if config_args: parser_kwargs['arguments'] += config_args diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py index 66c53cf148de..d13f306eda65 100755 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py @@ -188,13 +188,16 @@ class Contents(unittest.TestCase): self.failUnlessEqual(e.args[0], 0) zf = zipfile.ZipFile("seven.xpi", "r") names = zf.namelist() - # problem found in bug 664840 was that an addon + # the first problem found in bug 664840 was that cuddlefish.js + # (the loader) was stripped out on windows, due to a /-vs-\ bug + self.assertIn("resources/addon-sdk/lib/sdk/loader/cuddlefish.js", names) + # the second problem found in bug 664840 was that an addon # without an explicit tests/ directory would copy all files from # the package into a bogus JID-PKGNAME-tests/ directory, so check # for that testfiles = [fn for fn in names if "seven/tests" in fn] self.failUnlessEqual([], testfiles) - # another problem was that data files were being stripped from + # the third problem was that data files were being stripped from # the XPI. Note that data/ is only supposed to be included if a # module that actually gets used does a require("self") . self.assertIn("resources/seven/data/text.data", diff --git a/addon-sdk/source/python-lib/mozrunner/__init__.py b/addon-sdk/source/python-lib/mozrunner/__init__.py index 87c2c320fc58..2e5c57abd908 100644 --- a/addon-sdk/source/python-lib/mozrunner/__init__.py +++ b/addon-sdk/source/python-lib/mozrunner/__init__.py @@ -406,8 +406,7 @@ class Runner(object): def find_binary(self): """Finds the binary for self.names if one was not provided.""" binary = None - if sys.platform in ('linux2', 'sunos5', 'solaris') \ - or sys.platform.startswith('freebsd'): + if sys.platform in ('linux2', 'sunos5', 'solaris'): for name in reversed(self.names): binary = findInPath(name) elif os.name == 'nt' or sys.platform == 'cygwin': @@ -579,8 +578,7 @@ class FirefoxRunner(Runner): def names(self): if sys.platform == 'darwin': return ['firefox', 'nightly', 'shiretoko'] - if sys.platform in ('linux2', 'sunos5', 'solaris') \ - or sys.platform.startswith('freebsd'): + if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): return ['firefox', 'mozilla-firefox', 'iceweasel'] if os.name == 'nt' or sys.platform == 'cygwin': return ['firefox'] diff --git a/addon-sdk/source/python-lib/mozrunner/killableprocess.py b/addon-sdk/source/python-lib/mozrunner/killableprocess.py index 92f7ac3779fe..892ed87fc85b 100644 --- a/addon-sdk/source/python-lib/mozrunner/killableprocess.py +++ b/addon-sdk/source/python-lib/mozrunner/killableprocess.py @@ -257,8 +257,7 @@ class Popen(subprocess.Popen): self.kill(group) else: - if sys.platform in ('linux2', 'sunos5', 'solaris') \ - or sys.platform.startswith('freebsd'): + if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): def group_wait(timeout): try: os.waitpid(self.pid, 0) diff --git a/addon-sdk/source/test/addons/private-browsing-supported/main.js b/addon-sdk/source/test/addons/private-browsing-supported/main.js index 9e8ef5d3140b..34b9f588b48b 100644 --- a/addon-sdk/source/test/addons/private-browsing-supported/main.js +++ b/addon-sdk/source/test/addons/private-browsing-supported/main.js @@ -10,20 +10,15 @@ const { isGlobalPBSupported } = require('sdk/private-browsing/utils'); merge(module.exports, require('./test-tabs'), require('./test-page-mod'), + require('./test-selection'), + require('./test-panel'), require('./test-private-browsing'), isGlobalPBSupported ? require('./test-global-private-browsing') : {} ); // Doesn't make sense to test window-utils and windows on fennec, -// as there is only one window which is never private. Also ignore -// unsupported modules (panel, selection) -if (!app.is('Fennec')) { - merge(module.exports, - require('./test-selection'), - require('./test-panel'), - require('./test-window-tabs'), - require('./test-windows') - ); -} +// as there is only one window which is never private +if (!app.is('Fennec')) + merge(module.exports, require('./test-windows')); require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js b/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js index dbc0385bef7b..dac987713789 100644 --- a/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js +++ b/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js @@ -1,9 +1,12 @@ 'use strict'; const tabs = require('sdk/tabs'); +const { is } = require('sdk/system/xul-app'); const { isPrivate } = require('sdk/private-browsing'); const pbUtils = require('sdk/private-browsing/utils'); const { getOwnerWindow } = require('sdk/private-browsing/window/utils'); +const { promise: windowPromise, close, focus } = require('sdk/window/helpers'); +const { getMostRecentBrowserWindow } = require('sdk/window/utils'); exports.testPrivateTabsAreListed = function (assert, done) { let originalTabCount = tabs.length; @@ -29,5 +32,82 @@ exports.testPrivateTabsAreListed = function (assert, done) { tab.close(done); } }); -}; +} +exports.testOpenTabWithPrivateActiveWindowNoIsPrivateOption = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.ok(isPrivate(window), 'new window is private'); + + tabs.open({ + url: 'about:blank', + onOpen: function(tab) { + assert.ok(isPrivate(tab), 'new tab is private'); + assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} + +exports.testOpenTabWithNonPrivateActiveWindowNoIsPrivateOption = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.equal(isPrivate(window), false, 'new window is not private'); + + tabs.open({ + url: 'about:blank', + onOpen: function(tab) { + assert.equal(isPrivate(tab), false, 'new tab is not private'); + assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} + +exports.testOpenTabWithPrivateActiveWindowWithIsPrivateOptionTrue = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.ok(isPrivate(window), 'new window is private'); + + tabs.open({ + url: 'about:blank', + isPrivate: true, + onOpen: function(tab) { + assert.ok(isPrivate(tab), 'new tab is private'); + assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} + +exports.testOpenTabWithNonPrivateActiveWindowWithIsPrivateOptionFalse = function(assert, done) { + let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); + + windowPromise(window, 'load').then(focus).then(function (window) { + assert.equal(isPrivate(window), false, 'new window is not private'); + + tabs.open({ + url: 'about:blank', + isPrivate: false, + onOpen: function(tab) { + assert.equal(isPrivate(tab), false, 'new tab is not private'); + assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); + assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); + + close(window).then(done, assert.fail); + } + }) + }, assert.fail).then(null, assert.fail); +} diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js b/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js deleted file mode 100644 index 05e787a4f3b6..000000000000 --- a/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js +++ /dev/null @@ -1,85 +0,0 @@ -'use strict'; - -const tabs = require('sdk/tabs'); -const { isPrivate } = require('sdk/private-browsing'); -const { getOwnerWindow } = require('sdk/private-browsing/window/utils'); -const { promise: windowPromise, close, focus } = require('sdk/window/helpers'); -const { getMostRecentBrowserWindow } = require('sdk/window/utils'); - -exports.testOpenTabWithPrivateActiveWindowNoIsPrivateOption = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.ok(isPrivate(window), 'new window is private'); - - tabs.open({ - url: 'about:blank', - onOpen: function(tab) { - assert.ok(isPrivate(tab), 'new tab is private'); - assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} - -exports.testOpenTabWithNonPrivateActiveWindowNoIsPrivateOption = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.equal(isPrivate(window), false, 'new window is not private'); - - tabs.open({ - url: 'about:blank', - onOpen: function(tab) { - assert.equal(isPrivate(tab), false, 'new tab is not private'); - assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} - -exports.testOpenTabWithPrivateActiveWindowWithIsPrivateOptionTrue = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.ok(isPrivate(window), 'new window is private'); - - tabs.open({ - url: 'about:blank', - isPrivate: true, - onOpen: function(tab) { - assert.ok(isPrivate(tab), 'new tab is private'); - assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} - -exports.testOpenTabWithNonPrivateActiveWindowWithIsPrivateOptionFalse = function(assert, done) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false }); - - windowPromise(window, 'load').then(focus).then(function (window) { - assert.equal(isPrivate(window), false, 'new window is not private'); - - tabs.open({ - url: 'about:blank', - isPrivate: false, - onOpen: function(tab) { - assert.equal(isPrivate(tab), false, 'new tab is not private'); - assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private'); - assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same'); - - close(window).then(done, assert.fail); - } - }) - }, assert.fail).then(null, assert.fail); -} diff --git a/addon-sdk/source/test/addons/privileged-panel/main.js b/addon-sdk/source/test/addons/privileged-panel/main.js index 8992a0a39244..c010de9242be 100644 --- a/addon-sdk/source/test/addons/privileged-panel/main.js +++ b/addon-sdk/source/test/addons/privileged-panel/main.js @@ -4,14 +4,10 @@ "use strict"; -const app = require("sdk/system/xul-app"); - -exports["test addon globa"] = app.is("Firefox") ? testAddonGlobal : unsupported; - -function testAddonGlobal (assert, done) { - const { Panel } = require("sdk/panel") - const { data } = require("sdk/self") +const { Panel } = require("sdk/panel") +const { data } = require("sdk/self") +exports["test addon global"] = function(assert, done) { let panel = Panel({ contentURL: //"data:text/html,now?", data.url("./index.html"), @@ -21,14 +17,10 @@ function testAddonGlobal (assert, done) { done(); }, onError: function(error) { - assert.fail(Error("failed to recieve message")); + asser.fail(Error("failed to recieve message")); done(); } }); }; -function unsupported (assert) { - assert.pass("privileged-panel unsupported on platform"); -} - require("sdk/test/runner").runTestsFromModule(module); diff --git a/addon-sdk/source/test/favicon-helpers.js b/addon-sdk/source/test/favicon-helpers.js index 3656ae6748b1..1be7592b8846 100644 --- a/addon-sdk/source/test/favicon-helpers.js +++ b/addon-sdk/source/test/favicon-helpers.js @@ -13,18 +13,42 @@ const basePath = pathFor('ProfD'); const { atob } = Cu.import("resource://gre/modules/Services.jsm", {}); const historyService = Cc["@mozilla.org/browser/nav-history-service;1"] .getService(Ci.nsINavHistoryService); -const { events } = require('sdk/places/events'); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +const ObserverShimMethods = ['onBeginUpdateBatch', 'onEndUpdateBatch', + 'onVisit', 'onTitleChanged', 'onDeleteURI', 'onClearHistory', + 'onPageChanged', 'onDeleteVisits']; -function onFaviconChange (url, callback) { - function handler ({data, type}) { - if (type !== 'history-page-changed' || - data.url !== url || - data.property !== Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) - return; - events.off('data', handler); - callback(data.value); - } - events.on('data', handler); +/* + * Shims NavHistoryObserver + */ + +let noop = function () {} +let NavHistoryObserver = function () {}; +ObserverShimMethods.forEach(function (method) { + NavHistoryObserver.prototype[method] = noop; +}); +NavHistoryObserver.prototype.QueryInterface = XPCOMUtils.generateQI([ + Ci.nsINavHistoryObserver +]); + +/* + * Uses history observer to watch for an onPageChanged event, + * which detects when a favicon is updated in the registry. + */ +function onFaviconChange (uri, callback) { + let observer = Object.create(NavHistoryObserver.prototype, { + onPageChanged: { + value: function onPageChanged(aURI, aWhat, aValue, aGUID) { + if (aWhat !== Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) + return; + if (aURI.spec !== uri) + return; + historyService.removeObserver(this); + callback(aValue); + } + } + }); + historyService.addObserver(observer, false); } exports.onFaviconChange = onFaviconChange; diff --git a/addon-sdk/source/test/places-helper.js b/addon-sdk/source/test/places-helper.js index 8107b4eae0b5..8ea0af17c8ae 100644 --- a/addon-sdk/source/test/places-helper.js +++ b/addon-sdk/source/test/places-helper.js @@ -106,13 +106,6 @@ function addVisits (urls) { } exports.addVisits = addVisits; -function removeVisits (urls) { - [].concat(urls).map(url => { - hsrv.removePage(newURI(url)); - }); -} -exports.removeVisits = removeVisits; - // Creates a mozIVisitInfo object function createVisit (url) { let place = {} diff --git a/addon-sdk/source/test/tabs/test-firefox-tabs.js b/addon-sdk/source/test/tabs/test-firefox-tabs.js index fd59222719fc..1d5346ac783a 100644 --- a/addon-sdk/source/test/tabs/test-firefox-tabs.js +++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js @@ -949,6 +949,40 @@ exports.testOnLoadEventWithImage = function(test) { }); }; +exports.testOnPageShowEvent = function (test) { + test.waitUntilDone(); + + let firstUrl = 'data:text/html;charset=utf-8,First'; + let secondUrl = 'data:text/html;charset=utf-8,Second'; + + openBrowserWindow(function(window, browser) { + let counter = 0; + tabs.on('pageshow', function onPageShow(tab, persisted) { + counter++; + if (counter === 1) { + test.assert(!persisted, 'page should not be cached on initial load'); + tab.url = secondUrl; + } + else if (counter === 2) { + test.assert(!persisted, 'second test page should not be cached either'); + tab.attach({ + contentScript: 'setTimeout(function () { window.history.back(); }, 0)' + }); + } + else { + test.assert(persisted, 'when we get back to the fist page, it has to' + + 'come from cache'); + tabs.removeListener('pageshow', onPageShow); + closeBrowserWindow(window, function() test.done()); + } + }); + + tabs.open({ + url: firstUrl + }); + }); +}; + exports.testFaviconGetterDeprecation = function (test) { const { LoaderWithHookedConsole } = require("sdk/test/loader"); let { loader, messages } = LoaderWithHookedConsole(module); diff --git a/addon-sdk/source/test/test-api-utils.js b/addon-sdk/source/test/test-api-utils.js index f26682b6f989..f0a0849ac4b4 100644 --- a/addon-sdk/source/test/test-api-utils.js +++ b/addon-sdk/source/test/test-api-utils.js @@ -6,94 +6,92 @@ const apiUtils = require("sdk/deprecated/api-utils"); -exports.testPublicConstructor = function (assert) { +exports.testPublicConstructor = function (test) { function PrivateCtor() {} PrivateCtor.prototype = {}; let PublicCtor = apiUtils.publicConstructor(PrivateCtor); - assert.ok( + test.assert( PrivateCtor.prototype.isPrototypeOf(PublicCtor.prototype), "PrivateCtor.prototype should be prototype of PublicCtor.prototype" ); function testObj(useNew) { let obj = useNew ? new PublicCtor() : PublicCtor(); - assert.ok(obj instanceof PublicCtor, + test.assert(obj instanceof PublicCtor, "Object should be instance of PublicCtor"); - assert.ok(obj instanceof PrivateCtor, + test.assert(obj instanceof PrivateCtor, "Object should be instance of PrivateCtor"); - assert.ok(PublicCtor.prototype.isPrototypeOf(obj), + test.assert(PublicCtor.prototype.isPrototypeOf(obj), "PublicCtor's prototype should be prototype of object"); - assert.equal(obj.constructor, PublicCtor, + test.assertEqual(obj.constructor, PublicCtor, "Object constructor should be PublicCtor"); } testObj(true); testObj(false); }; -exports.testValidateOptionsEmpty = function (assert) { +exports.testValidateOptionsEmpty = function (test) { let val = apiUtils.validateOptions(null, {}); - - assert.deepEqual(val, {}); + assertObjsEqual(test, val, {}); val = apiUtils.validateOptions(null, { foo: {} }); - assert.deepEqual(val, {}); + assertObjsEqual(test, val, {}); val = apiUtils.validateOptions({}, {}); - assert.deepEqual(val, {}); + assertObjsEqual(test, val, {}); val = apiUtils.validateOptions({}, { foo: {} }); - assert.deepEqual(val, {}); + assertObjsEqual(test, val, {}); }; -exports.testValidateOptionsNonempty = function (assert) { +exports.testValidateOptionsNonempty = function (test) { let val = apiUtils.validateOptions({ foo: 123 }, {}); - assert.deepEqual(val, {}); + assertObjsEqual(test, val, {}); val = apiUtils.validateOptions({ foo: 123, bar: 456 }, { foo: {}, bar: {}, baz: {} }); - - assert.deepEqual(val, { foo: 123, bar: 456 }); + assertObjsEqual(test, val, { foo: 123, bar: 456 }); }; -exports.testValidateOptionsMap = function (assert) { +exports.testValidateOptionsMap = function (test) { let val = apiUtils.validateOptions({ foo: 3, bar: 2 }, { foo: { map: function (v) v * v }, bar: { map: function (v) undefined } }); - assert.deepEqual(val, { foo: 9, bar: undefined }); + assertObjsEqual(test, val, { foo: 9, bar: undefined }); }; -exports.testValidateOptionsMapException = function (assert) { +exports.testValidateOptionsMapException = function (test) { let val = apiUtils.validateOptions({ foo: 3 }, { foo: { map: function () { throw new Error(); }} }); - assert.deepEqual(val, { foo: 3 }); + assertObjsEqual(test, val, { foo: 3 }); }; -exports.testValidateOptionsOk = function (assert) { +exports.testValidateOptionsOk = function (test) { let val = apiUtils.validateOptions({ foo: 3, bar: 2, baz: 1 }, { foo: { ok: function (v) v }, bar: { ok: function (v) v } }); - assert.deepEqual(val, { foo: 3, bar: 2 }); + assertObjsEqual(test, val, { foo: 3, bar: 2 }); - assert.throws( + test.assertRaises( function () apiUtils.validateOptions({ foo: 2, bar: 2 }, { bar: { ok: function (v) v > 2 } }), - /^The option "bar" is invalid/, + 'The option "bar" is invalid.', "ok should raise exception on invalid option" ); - assert.throws( + test.assertRaises( function () apiUtils.validateOptions(null, { foo: { ok: function (v) v }}), - /^The option "foo" is invalid/, + 'The option "foo" is invalid.', "ok should raise exception on invalid option" ); }; -exports.testValidateOptionsIs = function (assert) { +exports.testValidateOptionsIs = function (test) { let opts = { array: [], boolean: true, @@ -116,137 +114,18 @@ exports.testValidateOptionsIs = function (assert) { undef2: { is: ["undefined"] } }; let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); + assertObjsEqual(test, val, opts); - assert.throws( + test.assertRaises( function () apiUtils.validateOptions(null, { foo: { is: ["object", "number"] } }), - /^The option "foo" must be one of the following types: object, number/, + 'The option "foo" must be one of the following types: object, number', "Invalid type should raise exception" ); }; -exports.testValidateOptionsIsWithExportedValue = function (assert) { - let { string, number, boolean, object } = apiUtils; - - let opts = { - boolean: true, - number: 1337, - object: {}, - string: "foo" - }; - let requirements = { - string: { is: string }, - number: { is: number }, - boolean: { is: boolean }, - object: { is: object } - }; - let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); - - // Test the types are optional by default - val = apiUtils.validateOptions({foo: 'bar'}, requirements); - assert.deepEqual(val, {}); -}; - -exports.testValidateOptionsIsWithEither = function (assert) { - let { string, number, boolean, either } = apiUtils; - let text = { is: either(string, number) }; - - let requirements = { - text: text, - boolOrText: { is: either(text, boolean) } - }; - - let val = apiUtils.validateOptions({text: 12}, requirements); - assert.deepEqual(val, {text: 12}); - - val = apiUtils.validateOptions({text: "12"}, requirements); - assert.deepEqual(val, {text: "12"}); - - val = apiUtils.validateOptions({boolOrText: true}, requirements); - assert.deepEqual(val, {boolOrText: true}); - - val = apiUtils.validateOptions({boolOrText: "true"}, requirements); - assert.deepEqual(val, {boolOrText: "true"}); - - val = apiUtils.validateOptions({boolOrText: 1}, requirements); - assert.deepEqual(val, {boolOrText: 1}); - - assert.throws( - () => apiUtils.validateOptions({text: true}, requirements), - /^The option "text" must be one of the following types/, - "Invalid type should raise exception" - ); - - assert.throws( - () => apiUtils.validateOptions({boolOrText: []}, requirements), - /^The option "boolOrText" must be one of the following types/, - "Invalid type should raise exception" - ); -}; - -exports.testValidateOptionsWithRequiredAndOptional = function (assert) { - let { string, number, required, optional } = apiUtils; - - let opts = { - number: 1337, - string: "foo" - }; - - let requirements = { - string: required(string), - number: number - }; - - let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); - - val = apiUtils.validateOptions({string: "foo"}, requirements); - assert.deepEqual(val, {string: "foo"}); - - assert.throws( - () => apiUtils.validateOptions({number: 10}, requirements), - /^The option "string" must be one of the following types/, - "Invalid type should raise exception" - ); - - // Makes string optional - requirements.string = optional(requirements.string); - - val = apiUtils.validateOptions({number: 10}, requirements), - assert.deepEqual(val, {number: 10}); - -}; - - - -exports.testValidateOptionsWithExportedValue = function (assert) { - let { string, number, boolean, object } = apiUtils; - - let opts = { - boolean: true, - number: 1337, - object: {}, - string: "foo" - }; - let requirements = { - string: string, - number: number, - boolean: boolean, - object: object - }; - let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); - - // Test the types are optional by default - val = apiUtils.validateOptions({foo: 'bar'}, requirements); - assert.deepEqual(val, {}); -}; - - -exports.testValidateOptionsMapIsOk = function (assert) { +exports.testValidateOptionsMapIsOk = function (test) { let [map, is, ok] = [false, false, false]; let val = apiUtils.validateOptions({ foo: 1337 }, { foo: { @@ -255,48 +134,48 @@ exports.testValidateOptionsMapIsOk = function (assert) { ok: function (v) v.length > 0 } }); - assert.deepEqual(val, { foo: "1337" }); + assertObjsEqual(test, val, { foo: "1337" }); let requirements = { foo: { is: ["object"], - ok: function () assert.fail("is should have caused us to throw by now") + ok: function () test.fail("is should have caused us to throw by now") } }; - assert.throws( + test.assertRaises( function () apiUtils.validateOptions(null, requirements), - /^The option "foo" must be one of the following types: object/, + 'The option "foo" must be one of the following types: object', "is should be used before ok is called" ); }; -exports.testValidateOptionsErrorMsg = function (assert) { - assert.throws( +exports.testValidateOptionsErrorMsg = function (test) { + test.assertRaises( function () apiUtils.validateOptions(null, { foo: { ok: function (v) v, msg: "foo!" } }), - /^foo!/, + "foo!", "ok should raise exception with customized message" ); }; -exports.testValidateMapWithMissingKey = function (assert) { +exports.testValidateMapWithMissingKey = function (test) { let val = apiUtils.validateOptions({ }, { foo: { map: function (v) v || "bar" } }); - assert.deepEqual(val, { foo: "bar" }); + assertObjsEqual(test, val, { foo: "bar" }); val = apiUtils.validateOptions({ }, { foo: { map: function (v) { throw "bar" } } }); - assert.deepEqual(val, { }); + assertObjsEqual(test, val, { }); }; -exports.testValidateMapWithMissingKeyAndThrown = function (assert) { +exports.testValidateMapWithMissingKeyAndThrown = function (test) { let val = apiUtils.validateOptions({}, { bar: { map: function(v) { throw "bar" } @@ -305,10 +184,10 @@ exports.testValidateMapWithMissingKeyAndThrown = function (assert) { map: function(v) "foo" } }); - assert.deepEqual(val, { baz: "foo" }); + assertObjsEqual(test, val, { baz: "foo" }); }; -exports.testAddIterator = function testAddIterator (assert) { +exports.testAddIterator = function testAddIterator(test) { let obj = {}; let keys = ["foo", "bar", "baz"]; let vals = [1, 2, 3]; @@ -324,20 +203,34 @@ exports.testAddIterator = function testAddIterator (assert) { let keysItr = []; for (let key in obj) keysItr.push(key); - - assert.equal(keysItr.length, keys.length, + test.assertEqual(keysItr.length, keys.length, "the keys iterator returns the correct number of items"); for (let i = 0; i < keys.length; i++) - assert.equal(keysItr[i], keys[i], "the key is correct"); + test.assertEqual(keysItr[i], keys[i], "the key is correct"); let valsItr = []; for each (let val in obj) valsItr.push(val); - assert.equal(valsItr.length, vals.length, + test.assertEqual(valsItr.length, vals.length, "the vals iterator returns the correct number of items"); for (let i = 0; i < vals.length; i++) - assert.equal(valsItr[i], vals[i], "the val is correct"); + test.assertEqual(valsItr[i], vals[i], "the val is correct"); }; -require('test').run(exports); +function assertObjsEqual(test, obj1, obj2) { + var items = 0; + for (let key in obj1) { + items++; + test.assert(key in obj2, "obj1 key should be present in obj2"); + test.assertEqual(obj2[key], obj1[key], "obj1 value should match obj2 value"); + } + for (let key in obj2) { + items++; + test.assert(key in obj1, "obj2 key should be present in obj1"); + test.assertEqual(obj1[key], obj2[key], "obj2 value should match obj1 value"); + } + if (!items) + test.assertEqual(JSON.stringify(obj1), JSON.stringify(obj2), + "obj1 should have same JSON representation as obj2"); +} diff --git a/addon-sdk/source/test/test-array.js b/addon-sdk/source/test/test-array.js index 8a9cb4402be8..af26770edbbe 100644 --- a/addon-sdk/source/test/test-array.js +++ b/addon-sdk/source/test/test-array.js @@ -5,67 +5,67 @@ const array = require('sdk/util/array'); -exports.testHas = function(assert) { +exports.testHas = function(test) { var testAry = [1, 2, 3]; - assert.equal(array.has([1, 2, 3], 1), true); - assert.equal(testAry.length, 3); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); - assert.equal(testAry[2], 3); - assert.equal(array.has(testAry, 2), true); - assert.equal(array.has(testAry, 3), true); - assert.equal(array.has(testAry, 4), false); - assert.equal(array.has(testAry, '1'), false); + test.assertEqual(array.has([1, 2, 3], 1), true); + test.assertEqual(testAry.length, 3); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); + test.assertEqual(testAry[2], 3); + test.assertEqual(array.has(testAry, 2), true); + test.assertEqual(array.has(testAry, 3), true); + test.assertEqual(array.has(testAry, 4), false); + test.assertEqual(array.has(testAry, '1'), false); }; -exports.testHasAny = function(assert) { +exports.testHasAny = function(test) { var testAry = [1, 2, 3]; - assert.equal(array.hasAny([1, 2, 3], [1]), true); - assert.equal(array.hasAny([1, 2, 3], [1, 5]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 1]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 2]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 3]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 4]), false); - assert.equal(testAry.length, 3); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); - assert.equal(testAry[2], 3); - assert.equal(array.hasAny(testAry, [2]), true); - assert.equal(array.hasAny(testAry, [3]), true); - assert.equal(array.hasAny(testAry, [4]), false); - assert.equal(array.hasAny(testAry), false); - assert.equal(array.hasAny(testAry, '1'), false); + test.assertEqual(array.hasAny([1, 2, 3], [1]), true); + test.assertEqual(array.hasAny([1, 2, 3], [1, 5]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 1]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 2]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 3]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 4]), false); + test.assertEqual(testAry.length, 3); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); + test.assertEqual(testAry[2], 3); + test.assertEqual(array.hasAny(testAry, [2]), true); + test.assertEqual(array.hasAny(testAry, [3]), true); + test.assertEqual(array.hasAny(testAry, [4]), false); + test.assertEqual(array.hasAny(testAry), false); + test.assertEqual(array.hasAny(testAry, '1'), false); }; -exports.testAdd = function(assert) { +exports.testAdd = function(test) { var testAry = [1]; - assert.equal(array.add(testAry, 1), false); - assert.equal(testAry.length, 1); - assert.equal(testAry[0], 1); - assert.equal(array.add(testAry, 2), true); - assert.equal(testAry.length, 2); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); + test.assertEqual(array.add(testAry, 1), false); + test.assertEqual(testAry.length, 1); + test.assertEqual(testAry[0], 1); + test.assertEqual(array.add(testAry, 2), true); + test.assertEqual(testAry.length, 2); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); }; -exports.testRemove = function(assert) { +exports.testRemove = function(test) { var testAry = [1, 2]; - assert.equal(array.remove(testAry, 3), false); - assert.equal(testAry.length, 2); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); - assert.equal(array.remove(testAry, 2), true); - assert.equal(testAry.length, 1); - assert.equal(testAry[0], 1); + test.assertEqual(array.remove(testAry, 3), false); + test.assertEqual(testAry.length, 2); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); + test.assertEqual(array.remove(testAry, 2), true); + test.assertEqual(testAry.length, 1); + test.assertEqual(testAry[0], 1); }; -exports.testFlatten = function(assert) { - assert.equal(array.flatten([1, 2, 3]).length, 3); - assert.equal(array.flatten([1, [2, 3]]).length, 3); - assert.equal(array.flatten([1, [2, [3]]]).length, 3); - assert.equal(array.flatten([[1], [[2, [3]]]]).length, 3); +exports.testFlatten = function(test) { + test.assertEqual(array.flatten([1, 2, 3]).length, 3); + test.assertEqual(array.flatten([1, [2, 3]]).length, 3); + test.assertEqual(array.flatten([1, [2, [3]]]).length, 3); + test.assertEqual(array.flatten([[1], [[2, [3]]]]).length, 3); }; -exports.testUnique = function(assert) { +exports.testUnique = function(test) { var Class = function () {}; var A = {}; var B = new Class(); @@ -73,31 +73,23 @@ exports.testUnique = function(assert) { var D = {}; var E = new Class(); - assert.deepEqual(array.unique([1,2,3,1,2]), [1,2,3]); - assert.deepEqual(array.unique([1,1,1,4,9,5,5]), [1,4,9,5]); - assert.deepEqual(array.unique([A, A, A, B, B, D]), [A,B,D]); - assert.deepEqual(array.unique([A, D, A, E, E, D, A, A, C]), [A, D, E, C]) + compareArray(array.unique([1,2,3,1,2]), [1,2,3]); + compareArray(array.unique([1,1,1,4,9,5,5]), [1,4,9,5]); + compareArray(array.unique([A, A, A, B, B, D]), [A,B,D]); + compareArray(array.unique([A, D, A, E, E, D, A, A, C]), [A, D, E, C]) + + function compareArray (a, b) { + test.assertEqual(a.length, b.length); + for (let i = 0; i < a.length; i++) { + test.assertEqual(a[i], b[i]); + } + } }; -exports.testUnion = function(assert) { - var Class = function () {}; - var A = {}; - var B = new Class(); - var C = [ 1, 2, 3 ]; - var D = {}; - var E = new Class(); - - assert.deepEqual(array.union([1, 2, 3],[7, 1, 2]), [1, 2, 3, 7]); - assert.deepEqual(array.union([1, 1, 1, 4, 9, 5, 5], [10, 1, 5]), [1, 4, 9, 5, 10]); - assert.deepEqual(array.union([A, B], [A, D]), [A, B, D]); - assert.deepEqual(array.union([A, D], [A, E], [E, D, A], [A, C]), [A, D, E, C]); -}; - -exports.testFind = function(assert) { +exports.testFind = function(test) { let isOdd = (x) => x % 2; - assert.equal(array.find([2, 4, 5, 7, 8, 9], isOdd), 5); - assert.equal(array.find([2, 4, 6, 8], isOdd), undefined); - assert.equal(array.find([2, 4, 6, 8], isOdd, null), null); + test.assertEqual(array.find([2, 4, 5, 7, 8, 9], isOdd), 5); + test.assertEqual(array.find([2, 4, 6, 8], isOdd), undefined); + test.assertEqual(array.find([2, 4, 6, 8], isOdd, null), null); }; -require('test').run(exports); diff --git a/addon-sdk/source/test/test-browser-events.js b/addon-sdk/source/test/test-browser-events.js index b4d8a1b016da..afa7180977d5 100644 --- a/addon-sdk/source/test/test-browser-events.js +++ b/addon-sdk/source/test/test-browser-events.js @@ -4,12 +4,6 @@ "use strict"; -module.metadata = { - engines: { - "Firefox": "*" - } -}; - const { Loader } = require("sdk/test/loader"); const { open, getMostRecentBrowserWindow, getOuterId } = require("sdk/window/utils"); const { setTimeout } = require("sdk/timers"); diff --git a/addon-sdk/source/test/test-content-events.js b/addon-sdk/source/test/test-content-events.js index f4a7b1644792..43d4fadf9847 100644 --- a/addon-sdk/source/test/test-content-events.js +++ b/addon-sdk/source/test/test-content-events.js @@ -44,17 +44,13 @@ exports["test multiple tabs"] = function(assert, done) { on(events, "data", function({type, target, timeStamp}) { // ignore about:blank pages and *-document-global-created // events that are not very consistent. - // ignore http:// requests, as Fennec's `about:home` page - // displays add-ons a user could install if (target.URL !== "about:blank" && - target.URL !== "about:home" && - !target.URL.match(/^https?:\/\//i) && type !== "chrome-document-global-created" && type !== "content-document-global-created") actual.push(type + " -> " + target.URL) }); - let window = getMostRecentBrowserWindow(); + let window = getMostRecentBrowserWindow(); let firstTab = open("data:text/html,first-tab", window); when("pageshow", firstTab). diff --git a/addon-sdk/source/test/test-path.js b/addon-sdk/source/test/test-path.js index f0b9e06cb0a2..9e9cb5c53190 100644 --- a/addon-sdk/source/test/test-path.js +++ b/addon-sdk/source/test/test-path.js @@ -429,3 +429,8 @@ if (isWindows) { }; require('test').run(exports); + +// Test disabled on OSX because of bug 891698 +if (require("sdk/system/runtime").OS == "Darwin") + module.exports = {}; + diff --git a/addon-sdk/source/test/test-places-events.js b/addon-sdk/source/test/test-places-events.js deleted file mode 100644 index 67f82188d8fd..000000000000 --- a/addon-sdk/source/test/test-places-events.js +++ /dev/null @@ -1,292 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - 'engines': { - 'Firefox': '*' - } -}; - -const { Cc, Ci } = require('chrome'); -const { defer, all } = require('sdk/core/promise'); -const { filter } = require('sdk/event/utils'); -const { on, off } = require('sdk/event/core'); -const { events } = require('sdk/places/events'); -const { setTimeout } = require('sdk/timers'); -const { before, after } = require('sdk/test/utils'); -const { - search -} = require('sdk/places/history'); -const { - invalidResolve, invalidReject, createTree, createBookmark, - compareWithHost, addVisits, resetPlaces, createBookmarkItem, - removeVisits -} = require('./places-helper'); -const { save, MENU, UNSORTED } = require('sdk/places/bookmarks'); -const { promisedEmitter } = require('sdk/places/utils'); - -exports['test bookmark-item-added'] = function (assert, done) { - function handler ({type, data}) { - if (type !== 'bookmark-item-added') return; - if (data.title !== 'bookmark-added-title') return; - - assert.equal(type, 'bookmark-item-added', 'correct type in bookmark-added event'); - assert.equal(data.type, 'bookmark', 'correct data in bookmark-added event'); - assert.ok(data.id != null, 'correct data in bookmark-added event'); - assert.ok(data.parentId != null, 'correct data in bookmark-added event'); - assert.ok(data.index != null, 'correct data in bookmark-added event'); - assert.equal(data.url, 'http://moz.com/', 'correct data in bookmark-added event'); - assert.ok(data.dateAdded != null, 'correct data in bookmark-added event'); - events.off('data', handler); - done(); - } - events.on('data', handler); - createBookmark({ title: 'bookmark-added-title' }); -}; - -exports['test bookmark-item-changed'] = function (assert, done) { - let id; - let complete = makeCompleted(done); - function handler ({type, data}) { - if (type !== 'bookmark-item-changed') return; - if (data.id !== id) return; - assert.equal(type, 'bookmark-item-changed', - 'correct type in bookmark-item-changed event'); - assert.equal(data.type, 'bookmark', - 'correct data in bookmark-item-changed event'); - assert.equal(data.property, 'title', - 'correct property in bookmark-item-changed event'); - assert.equal(data.value, 'bookmark-changed-title-2', - 'correct value in bookmark-item-changed event'); - assert.ok(data.id === id, 'correct id in bookmark-item-changed event'); - assert.ok(data.parentId != null, 'correct data in bookmark-added event'); - - events.off('data', handler); - complete(); - } - events.on('data', handler); - - createBookmarkItem({ title: 'bookmark-changed-title' }).then(item => { - id = item.id; - item.title = 'bookmark-changed-title-2'; - return saveP(item); - }).then(complete); -}; - -exports['test bookmark-item-moved'] = function (assert, done) { - let id; - let complete = makeCompleted(done); - function handler ({type, data}) { - if (type !== 'bookmark-item-moved') return; - if (data.id !== id) return; - assert.equal(type, 'bookmark-item-moved', - 'correct type in bookmark-item-moved event'); - assert.equal(data.type, 'bookmark', - 'correct data in bookmark-item-moved event'); - assert.ok(data.id === id, 'correct id in bookmark-item-moved event'); - assert.equal(data.previousParentId, UNSORTED.id, - 'correct previousParentId'); - assert.equal(data.currentParentId, MENU.id, - 'correct currentParentId'); - assert.equal(data.previousIndex, 0, 'correct previousIndex'); - assert.equal(data.currentIndex, 0, 'correct currentIndex'); - - events.off('data', handler); - complete(); - } - events.on('data', handler); - - createBookmarkItem({ - title: 'bookmark-moved-title', - group: UNSORTED - }).then(item => { - id = item.id; - item.group = MENU; - return saveP(item); - }).then(complete); -}; - -exports['test bookmark-item-removed'] = function (assert, done) { - let id; - let complete = makeCompleted(done); - function handler ({type, data}) { - if (type !== 'bookmark-item-removed') return; - if (data.id !== id) return; - assert.equal(type, 'bookmark-item-removed', - 'correct type in bookmark-item-removed event'); - assert.equal(data.type, 'bookmark', - 'correct data in bookmark-item-removed event'); - assert.ok(data.id === id, 'correct id in bookmark-item-removed event'); - assert.equal(data.parentId, UNSORTED.id, - 'correct parentId in bookmark-item-removed'); - assert.equal(data.url, 'http://moz.com/', - 'correct url in bookmark-item-removed event'); - assert.equal(data.index, 0, - 'correct index in bookmark-item-removed event'); - - events.off('data', handler); - complete(); - } - events.on('data', handler); - - createBookmarkItem({ - title: 'bookmark-item-remove-title', - group: UNSORTED - }).then(item => { - id = item.id; - item.remove = true; - return saveP(item); - }).then(complete); -}; - -exports['test bookmark-item-visited'] = function (assert, done) { - let id; - let complete = makeCompleted(done); - function handler ({type, data}) { - if (type !== 'bookmark-item-visited') return; - if (data.id !== id) return; - assert.equal(type, 'bookmark-item-visited', - 'correct type in bookmark-item-visited event'); - assert.ok(data.id === id, 'correct id in bookmark-item-visited event'); - assert.equal(data.parentId, UNSORTED.id, - 'correct parentId in bookmark-item-visited'); - assert.ok(data.transitionType != null, - 'has a transition type in bookmark-item-visited event'); - assert.ok(data.time != null, - 'has a time in bookmark-item-visited event'); - assert.ok(data.visitId != null, - 'has a visitId in bookmark-item-visited event'); - assert.equal(data.url, 'http://bookmark-item-visited.com/', - 'correct url in bookmark-item-visited event'); - - events.off('data', handler); - complete(); - } - events.on('data', handler); - - createBookmarkItem({ - title: 'bookmark-item-visited', - url: 'http://bookmark-item-visited.com/' - }).then(item => { - id = item.id; - return addVisits('http://bookmark-item-visited.com/'); - }).then(complete); -}; - -exports['test history-start-batch, history-end-batch, history-start-clear'] = function (assert, done) { - let complete = makeCompleted(done, 4); - let startEvent = filter(events, ({type}) => type === 'history-start-batch'); - let endEvent = filter(events, ({type}) => type === 'history-end-batch'); - let clearEvent = filter(events, ({type}) => type === 'history-start-clear'); - function startHandler ({type, data}) { - assert.pass('history-start-batch called'); - assert.equal(type, 'history-start-batch', - 'history-start-batch has correct type'); - off(startEvent, 'data', startHandler); - on(endEvent, 'data', endHandler); - complete(); - } - function endHandler ({type, data}) { - assert.pass('history-end-batch called'); - assert.equal(type, 'history-end-batch', - 'history-end-batch has correct type'); - off(endEvent, 'data', endHandler); - complete(); - } - function clearHandler ({type, data}) { - assert.pass('history-start-clear called'); - assert.equal(type, 'history-start-clear', - 'history-start-clear has correct type'); - off(clearEvent, 'data', clearHandler); - complete(); - } - - on(startEvent, 'data', startHandler); - on(clearEvent, 'data', clearHandler); - - createBookmark().then(() => { - resetPlaces(complete); - }) -}; - -exports['test history-visit, history-title-changed'] = function (assert, done) { - let complete = makeCompleted(() => { - off(titleEvents, 'data', titleHandler); - off(visitEvents, 'data', visitHandler); - done(); - }, 6); - let visitEvents = filter(events, ({type}) => type === 'history-visit'); - let titleEvents = filter(events, ({type}) => type === 'history-title-changed'); - - let urls = ['http://moz.com/', 'http://firefox.com/', 'http://mdn.com/']; - - function visitHandler ({type, data}) { - assert.equal(type, 'history-visit', 'correct type in history-visit'); - assert.ok(~urls.indexOf(data.url), 'history-visit has correct url'); - assert.ok(data.visitId != null, 'history-visit has a visitId'); - assert.ok(data.time != null, 'history-visit has a time'); - assert.ok(data.sessionId != null, 'history-visit has a sessionId'); - assert.ok(data.referringId != null, 'history-visit has a referringId'); - assert.ok(data.transitionType != null, 'history-visit has a transitionType'); - complete(); - } - - function titleHandler ({type, data}) { - assert.equal(type, 'history-title-changed', - 'correct type in history-title-changed'); - assert.ok(~urls.indexOf(data.url), - 'history-title-changed has correct url'); - assert.ok(data.title, 'history-title-changed has title'); - complete(); - } - - on(titleEvents, 'data', titleHandler); - on(visitEvents, 'data', visitHandler); - addVisits(urls); -} - -exports['test history-delete-url'] = function (assert, done) { - let complete = makeCompleted(() => { - events.off('data', handler); - done(); - }, 3); - let urls = ['http://moz.com/', 'http://firefox.com/', 'http://mdn.com/']; - function handler({type, data}) { - if (type !== 'history-delete-url') return; - assert.equal(type, 'history-delete-url', - 'history-delete-url has correct type'); - assert.ok(~urls.indexOf(data.url), 'history-delete-url has correct url'); - complete(); - } - - events.on('data', handler); - addVisits(urls).then(() => { - removeVisits(urls); - }); -}; - -exports['test history-page-changed'] = function (assert) { - assert.pass('history-page-changed tested in test-places-favicons'); -}; - -exports['test history-delete-visits'] = function (assert) { - assert.pass('TODO test history-delete-visits'); -}; - -before(exports, (name, assert, done) => resetPlaces(done)); -after(exports, (name, assert, done) => resetPlaces(done)); - -function saveP () { - return promisedEmitter(save.apply(null, Array.slice(arguments))); -} - -function makeCompleted (done, countTo) { - let count = 0; - countTo = countTo || 2; - return function () { - if (++count === countTo) done(); - }; -} -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-tabs-common.js b/addon-sdk/source/test/test-tabs-common.js index 247788880634..b225b8ef47ab 100644 --- a/addon-sdk/source/test/test-tabs-common.js +++ b/addon-sdk/source/test/test-tabs-common.js @@ -457,100 +457,3 @@ exports.testTabReload = function(test) { } }); }; - -exports.testOnPageShowEvent = function (test) { - test.waitUntilDone(); - - let events = []; - let firstUrl = 'data:text/html;charset=utf-8,First'; - let secondUrl = 'data:text/html;charset=utf-8,Second'; - - let counter = 0; - function onPageShow (tab, persisted) { - events.push('pageshow'); - counter++; - if (counter === 1) { - test.assertEqual(persisted, false, 'page should not be cached on initial load'); - tab.url = secondUrl; - } - else if (counter === 2) { - test.assertEqual(persisted, false, 'second test page should not be cached either'); - tab.attach({ - contentScript: 'setTimeout(function () { window.history.back(); }, 0)' - }); - } - else { - test.assertEqual(persisted, true, 'when we get back to the fist page, it has to' + - 'come from cache'); - tabs.removeListener('pageshow', onPageShow); - tabs.removeListener('open', onOpen); - tabs.removeListener('ready', onReady); - tab.close(() => { - ['open', 'ready', 'pageshow', 'ready', - 'pageshow', 'pageshow'].map((type, i) => { - test.assertEqual(type, events[i], 'correct ordering of events'); - }); - test.done() - }); - } - } - - function onOpen () events.push('open'); - function onReady () events.push('ready'); - - tabs.on('pageshow', onPageShow); - tabs.on('open', onOpen); - tabs.on('ready', onReady); - tabs.open({ - url: firstUrl - }); -}; - -exports.testOnPageShowEventDeclarative = function (test) { - test.waitUntilDone(); - - let events = []; - let firstUrl = 'data:text/html;charset=utf-8,First'; - let secondUrl = 'data:text/html;charset=utf-8,Second'; - - let counter = 0; - function onPageShow (tab, persisted) { - events.push('pageshow'); - counter++; - if (counter === 1) { - test.assertEqual(persisted, false, 'page should not be cached on initial load'); - tab.url = secondUrl; - } - else if (counter === 2) { - test.assertEqual(persisted, false, 'second test page should not be cached either'); - tab.attach({ - contentScript: 'setTimeout(function () { window.history.back(); }, 0)' - }); - } - else { - test.assertEqual(persisted, true, 'when we get back to the fist page, it has to' + - 'come from cache'); - tabs.removeListener('pageshow', onPageShow); - tabs.removeListener('open', onOpen); - tabs.removeListener('ready', onReady); - tab.close(() => { - ['open', 'ready', 'pageshow', 'ready', - 'pageshow', 'pageshow'].map((type, i) => { - test.assertEqual(type, events[i], 'correct ordering of events'); - }); - test.done() - }); - } - } - - function onOpen () events.push('open'); - function onReady () events.push('ready'); - - tabs.open({ - url: firstUrl, - onPageShow: onPageShow, - onOpen: onOpen, - onReady: onReady - }); -}; - diff --git a/addon-sdk/source/test/test-url.js b/addon-sdk/source/test/test-url.js index bfd55572a546..6f383a37b237 100644 --- a/addon-sdk/source/test/test-url.js +++ b/addon-sdk/source/test/test-url.js @@ -3,15 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; -const { - URL, - toFilename, - fromFilename, - isValidURI, - getTLD, - DataURL, - isLocalURL } = require('sdk/url'); - +const { URL, toFilename, fromFilename, isValidURI, getTLD, DataURL } = require('sdk/url'); const { pathFor } = require('sdk/system'); const file = require('sdk/io/file'); const tabs = require('sdk/tabs'); @@ -71,11 +63,11 @@ exports.testParseHttpSearchAndHash = function (assert) { var info = URL('https://www.moz.com/some/page.html'); assert.equal(info.hash, ''); assert.equal(info.search, ''); - + var hashOnly = URL('https://www.sub.moz.com/page.html#justhash'); assert.equal(hashOnly.search, ''); assert.equal(hashOnly.hash, '#justhash'); - + var queryOnly = URL('https://www.sub.moz.com/page.html?my=query'); assert.equal(queryOnly.search, '?my=query'); assert.equal(queryOnly.hash, ''); @@ -83,11 +75,11 @@ exports.testParseHttpSearchAndHash = function (assert) { var qMark = URL('http://www.moz.org?'); assert.equal(qMark.search, ''); assert.equal(qMark.hash, ''); - + var hash = URL('http://www.moz.org#'); assert.equal(hash.search, ''); assert.equal(hash.hash, ''); - + var empty = URL('http://www.moz.org?#'); assert.equal(hash.search, ''); assert.equal(hash.hash, ''); @@ -355,39 +347,6 @@ exports.testWindowLocationMatch = function (assert, done) { }) }; -exports.testURLInRegExpTest = function(assert) { - let url = 'https://mozilla.org'; - assert.equal((new RegExp(url).test(URL(url))), true, 'URL instances work in a RegExp test'); -} - -exports.testLocalURL = function(assert) { - [ - 'data:text/html;charset=utf-8,foo and bar', - 'data:text/plain,foo and bar', - 'resource://gre/modules/commonjs/', - 'chrome://browser/content/browser.xul' - ].forEach(aUri => { - assert.ok(isLocalURL(aUri), aUri + ' is a Local URL'); - }) - -} - -exports.testLocalURLwithRemoteURL = function(assert) { - validURIs().filter(url => !url.startsWith('data:')).forEach(aUri => { - assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); - }); -} - -exports.testLocalURLwithInvalidURL = function(assert) { - invalidURIs().concat([ - 'data:foo and bar', - 'resource:// must fail', - 'chrome:// here too' - ]).forEach(aUri => { - assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); - }); -} - function validURIs() { return [ 'http://foo.com/blah_blah', From 4e3fd53fc6c0679865d64607150c0f966adc2b15 Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Thu, 8 Aug 2013 15:56:22 -0400 Subject: [PATCH 11/40] Bug 853388: Save and load XPIProvider state to/from a JSON file; r=unfocused --- toolkit/mozapps/extensions/XPIProvider.jsm | 310 ++-- .../mozapps/extensions/XPIProviderUtils.js | 1276 ++++++----------- .../extensions/test/xpcshell/head_addons.js | 54 + .../test/xpcshell/test_badschema.js | 13 +- .../test/xpcshell/test_blocklistchange.js | 26 +- .../test/xpcshell/test_bootstrap.js | 2 - .../test/xpcshell/test_bug559800.js | 2 - .../test/xpcshell/test_bug659772.js | 16 +- .../extensions/test/xpcshell/test_corrupt.js | 2 +- .../xpcshell/test_corrupt_strictcompat.js | 2 +- .../test/xpcshell/test_db_sanity.js | 181 --- .../extensions/test/xpcshell/test_locked.js | 15 +- .../extensions/test/xpcshell/test_locked2.js | 12 +- .../test/xpcshell/test_locked_strictcompat.js | 12 +- .../extensions/test/xpcshell/test_migrate2.js | 2 +- .../extensions/test/xpcshell/test_migrate4.js | 12 +- .../extensions/test/xpcshell/test_migrate5.js | 2 +- .../extensions/test/xpcshell/test_startup.js | 4 +- .../extensions/test/xpcshell/test_syncGUID.js | 3 +- .../extensions/test/xpcshell/xpcshell.ini | 27 +- 20 files changed, 686 insertions(+), 1287 deletions(-) delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index b29db7b388f0..ca763ec78741 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -78,7 +78,7 @@ const DIR_STAGE = "staged"; const DIR_XPI_STAGE = "staged-xpis"; const DIR_TRASH = "trash"; -const FILE_DATABASE = "extensions.sqlite"; +const FILE_DATABASE = "extensions.json"; const FILE_OLD_CACHE = "extensions.cache"; const FILE_INSTALL_MANIFEST = "install.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; @@ -120,7 +120,12 @@ const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; // or calculated const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled", "sourceURI", "applyBackgroundUpdates", - "releaseNotesURI", "isForeignInstall", "syncGUID"]; + "releaseNotesURI", "foreignInstall", "syncGUID"]; +// Properties to cache and reload when an addon installation is pending +const PENDING_INSTALL_METADATA = + ["syncGUID", "targetApplications", "userDisabled", "softDisabled", + "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", + "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]; // Note: When adding/changing/removing items here, remember to change the // DB schema version to ensure changes are picked up ASAP. @@ -169,12 +174,15 @@ var gGlobalScope = this; var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i; ["LOG", "WARN", "ERROR"].forEach(function(aName) { - this.__defineGetter__(aName, function logFuncGetter() { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + Object.defineProperty(this, aName, { + get: function logFuncGetter() { + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); - LogManager.getLogger("addons.xpi", this); - return this[aName]; - }) + LogManager.getLogger("addons.xpi", this); + return this[aName]; + }, + configurable: true + }); }, this); @@ -197,9 +205,12 @@ function loadLazyObjects() { } for (let name of LAZY_OBJECTS) { - gGlobalScope.__defineGetter__(name, function lazyObjectGetter() { - let objs = loadLazyObjects(); - return objs[name]; + Object.defineProperty(gGlobalScope, name, { + get: function lazyObjectGetter() { + let objs = loadLazyObjects(); + return objs[name]; + }, + configurable: true }); } @@ -584,10 +595,13 @@ function isAddonDisabled(aAddon) { return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled; } -this.__defineGetter__("gRDF", function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); +Object.defineProperty(this, "gRDF", { + get: function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); + }, + configurable: true }); function EM_R(aProperty) { @@ -694,8 +708,11 @@ function loadManifestFromRDF(aUri, aStream) { }); PROP_LOCALE_MULTI.forEach(function(aProp) { - locale[aProp] = getPropertyArray(aDs, aSource, - aProp.substring(0, aProp.length - 1)); + // Don't store empty arrays + let props = getPropertyArray(aDs, aSource, + aProp.substring(0, aProp.length - 1)); + if (props.length > 0) + locale[aProp] = props; }); return locale; @@ -2518,31 +2535,33 @@ var XPIProvider = { newAddon.visible = !(newAddon.id in visibleAddons); // Update the database - XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor); - if (newAddon.visible) { - visibleAddons[newAddon.id] = newAddon; + let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, + aAddonState.descriptor); + if (newDBAddon.visible) { + visibleAddons[newDBAddon.id] = newDBAddon; // Remember add-ons that were changed during startup AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newAddon.id); + newDBAddon.id); // If this was the active theme and it is now disabled then enable the // default theme - if (aOldAddon.active && isAddonDisabled(newAddon)) + if (aOldAddon.active && isAddonDisabled(newDBAddon)) XPIProvider.enableDefaultTheme(); // If the new add-on is bootstrapped and active then call its install method - if (newAddon.active && newAddon.bootstrap) { + if (newDBAddon.active && newDBAddon.bootstrap) { // Startup cache must be flushed before calling the bootstrap script flushStartupCache(); - let installReason = Services.vc.compare(aOldAddon.version, newAddon.version) < 0 ? + let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, - "install", installReason, { oldVersion: aOldAddon.version }); + XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, + newDBAddon.type, file, "install", + installReason, { oldVersion: aOldAddon.version }); return false; } @@ -2569,7 +2588,7 @@ var XPIProvider = { function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) { LOG("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor); - aOldAddon._descriptor = aAddonState.descriptor; + aOldAddon.descriptor = aAddonState.descriptor; aOldAddon.visible = !(aOldAddon.id in visibleAddons); // Update the database @@ -2630,8 +2649,7 @@ var XPIProvider = { // If it should be active then mark it as active otherwise unload // its scope if (!isAddonDisabled(aOldAddon)) { - aOldAddon.active = true; - XPIDatabase.updateAddonActive(aOldAddon); + XPIDatabase.updateAddonActive(aOldAddon, true); } else { XPIProvider.unloadBootstrapScope(newAddon.id); @@ -2690,8 +2708,7 @@ var XPIProvider = { AddonManagerPrivate.addStartupChange(change, aOldAddon.id); if (aOldAddon.bootstrap) { // Update the add-ons active state - aOldAddon.active = !isDisabled; - XPIDatabase.updateAddonActive(aOldAddon); + XPIDatabase.updateAddonActive(aOldAddon, !isDisabled); } else { changed = true; @@ -2713,17 +2730,15 @@ var XPIProvider = { /** * Called when an add-on has been removed. * - * @param aInstallLocation - * The install location containing the add-on * @param aOldAddon * The AddonInternal as it appeared the last time the application * ran * @return a boolean indicating if flushing caches is required to complete * changing this add-on */ - function removeMetadata(aInstallLocation, aOldAddon) { + function removeMetadata(aOldAddon) { // This add-on has disappeared - LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation); + LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location); XPIDatabase.removeAddonMetadata(aOldAddon); // Remember add-ons that were uninstalled during startup @@ -2886,9 +2901,10 @@ var XPIProvider = { newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon)) } + let newDBAddon = null; try { // Update the database. - XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); + newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); } catch (e) { // Failing to write the add-on into the database is non-fatal, the @@ -2899,36 +2915,36 @@ var XPIProvider = { return false; } - if (newAddon.visible) { + if (newDBAddon.visible) { // Remember add-ons that were first detected during startup. if (isDetectedInstall) { // If a copy from a higher priority location was removed then this // add-on has changed if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED) - .indexOf(newAddon.id) != -1) { + .indexOf(newDBAddon.id) != -1) { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newAddon.id); + newDBAddon.id); } else { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, - newAddon.id); + newDBAddon.id); } } // Note if any visible add-on is not in the application install location - if (newAddon._installLocation.name != KEY_APP_GLOBAL) + if (newDBAddon._installLocation.name != KEY_APP_GLOBAL) XPIProvider.allAppGlobal = false; - visibleAddons[newAddon.id] = newAddon; + visibleAddons[newDBAddon.id] = newDBAddon; let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL; let extraParams = {}; // If we're hiding a bootstrapped add-on then call its uninstall method - if (newAddon.id in oldBootstrappedAddons) { - let oldBootstrap = oldBootstrappedAddons[newAddon.id]; + if (newDBAddon.id in oldBootstrappedAddons) { + let oldBootstrap = oldBootstrappedAddons[newDBAddon.id]; extraParams.oldVersion = oldBootstrap.version; - XPIProvider.bootstrappedAddons[newAddon.id] = oldBootstrap; + XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap; // If the old version is the same as the new version, or we're // recovering from a corrupt DB, don't call uninstall and install @@ -2936,7 +2952,7 @@ var XPIProvider = { if (sameVersion || !isNewInstall) return false; - installReason = Services.vc.compare(oldBootstrap.version, newAddon.version) < 0 ? + installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; @@ -2944,27 +2960,27 @@ var XPIProvider = { createInstance(Ci.nsIFile); oldAddonFile.persistentDescriptor = oldBootstrap.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version, + XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version, oldBootstrap.type, oldAddonFile, "uninstall", - installReason, { newVersion: newAddon.version }); - XPIProvider.unloadBootstrapScope(newAddon.id); + installReason, { newVersion: newDBAddon.version }); + XPIProvider.unloadBootstrapScope(newDBAddon.id); // If the new add-on is bootstrapped then we must flush the caches // before calling the new bootstrap script - if (newAddon.bootstrap) + if (newDBAddon.bootstrap) flushStartupCache(); } - if (!newAddon.bootstrap) + if (!newDBAddon.bootstrap) return true; // Visible bootstrapped add-ons need to have their install method called let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, + XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, newDBAddon.type, file, "install", installReason, extraParams); - if (!newAddon.active) - XPIProvider.unloadBootstrapScope(newAddon.id); + if (!newDBAddon.active) + XPIProvider.unloadBootstrapScope(newDBAddon.id); } return false; @@ -3034,7 +3050,7 @@ var XPIProvider = { changed = updateMetadata(installLocation, aOldAddon, addonState) || changed; } - else if (aOldAddon._descriptor != addonState.descriptor) { + else if (aOldAddon.descriptor != addonState.descriptor) { changed = updateDescriptor(installLocation, aOldAddon, addonState) || changed; } @@ -3047,7 +3063,7 @@ var XPIProvider = { XPIProvider.allAppGlobal = false; } else { - changed = removeMetadata(installLocation.name, aOldAddon) || changed; + changed = removeMetadata(aOldAddon) || changed; } }, this); } @@ -3071,7 +3087,7 @@ var XPIProvider = { knownLocations.forEach(function(aLocation) { let addons = XPIDatabase.getAddonsInLocation(aLocation); addons.forEach(function(aOldAddon) { - changed = removeMetadata(aLocation, aOldAddon) || changed; + changed = removeMetadata(aOldAddon) || changed; }, this); }, this); @@ -3465,7 +3481,7 @@ var XPIProvider = { let results = [createWrapper(a) for each (a in aAddons)]; XPIProvider.installs.forEach(function(aInstall) { if (aInstall.state == AddonManager.STATE_INSTALLED && - !(aInstall.addon instanceof DBAddonInternal)) + !(aInstall.addon.inDatabase)) results.push(createWrapper(aInstall.addon)); }); aCallback(results); @@ -3783,7 +3799,7 @@ var XPIProvider = { // This wouldn't normally be called for an already installed add-on (except // for forming the operationsRequiringRestart flags) so is really here as // a safety measure. - if (aAddon instanceof DBAddonInternal) + if (aAddon.inDatabase) return false; // If we have an AddonInstall for this add-on then we can see if there is @@ -4032,7 +4048,7 @@ var XPIProvider = { updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only update addon states for installed addons."); if (aUserDisabled !== undefined && aSoftDisabled !== undefined) { throw new Error("Cannot change userDisabled and softDisabled at the " + @@ -4105,8 +4121,7 @@ var XPIProvider = { } if (!needsRestart) { - aAddon.active = !isDisabled; - XPIDatabase.updateAddonActive(aAddon); + XPIDatabase.updateAddonActive(aAddon, !isDisabled); if (isDisabled) { if (aAddon.bootstrap) { let file = aAddon._installLocation.getLocationForID(aAddon.id); @@ -4142,7 +4157,7 @@ var XPIProvider = { * location that does not allow it */ uninstallAddon: function XPI_uninstallAddon(aAddon) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only uninstall installed addons."); if (aAddon._installLocation.locked) @@ -4184,8 +4199,7 @@ var XPIProvider = { AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false); if (!isAddonDisabled(aAddon) && !XPIProvider.enableRequiresRestart(aAddon)) { - aAddon.active = true; - XPIDatabase.updateAddonActive(aAddon); + XPIDatabase.updateAddonActive(aAddon, true); } if (aAddon.bootstrap) { @@ -4255,7 +4269,7 @@ var XPIProvider = { * The DBAddonInternal to cancel uninstall for */ cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only cancel uninstall for installed addons."); cleanStagingDir(aAddon._installLocation.getStagingDir(), [aAddon.id]); @@ -5244,7 +5258,7 @@ AddonInstall.prototype = { // Point the add-on to its extracted files as the xpi may get deleted this.addon._sourceBundle = stagedAddon; - // Cache the AddonInternal as it may have updated compatibiltiy info + // Cache the AddonInternal as it may have updated compatibility info let stagedJSON = stagedAddon.clone(); stagedJSON.leafName = this.addon.id + ".json"; if (stagedJSON.exists()) @@ -5313,8 +5327,7 @@ AddonInstall.prototype = { } if (!isUpgrade && this.existingAddon.active) { - this.existingAddon.active = false; - XPIDatabase.updateAddonActive(this.existingAddon); + XPIDatabase.updateAddonActive(this.existingAddon, false); } } @@ -5330,51 +5343,45 @@ AddonInstall.prototype = { this.addon.updateDate = recursiveLastModifiedTime(file); this.addon.visible = true; if (isUpgrade) { - XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, - file.persistentDescriptor); + this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, + file.persistentDescriptor); } else { this.addon.installDate = this.addon.updateDate; this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon)) - XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); + this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); } - // Retrieve the new DBAddonInternal for the add-on we just added - let self = this; - XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name, - function startInstall_getAddonInLocation(a) { - self.addon = a; - let extraParams = {}; - if (self.existingAddon) { - extraParams.oldVersion = self.existingAddon.version; - } + let extraParams = {}; + if (this.existingAddon) { + extraParams.oldVersion = this.existingAddon.version; + } - if (self.addon.bootstrap) { - XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, - self.addon.type, file, "install", + if (this.addon.bootstrap) { + XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, + this.addon.type, file, "install", + reason, extraParams); + } + + AddonManagerPrivate.callAddonListeners("onInstalled", + createWrapper(this.addon)); + + LOG("Install of " + this.sourceURI.spec + " completed."); + this.state = AddonManager.STATE_INSTALLED; + AddonManagerPrivate.callInstallListeners("onInstallEnded", + this.listeners, this.wrapper, + createWrapper(this.addon)); + + if (this.addon.bootstrap) { + if (this.addon.active) { + XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, + this.addon.type, file, "startup", reason, extraParams); } - - AddonManagerPrivate.callAddonListeners("onInstalled", - createWrapper(self.addon)); - - LOG("Install of " + self.sourceURI.spec + " completed."); - self.state = AddonManager.STATE_INSTALLED; - AddonManagerPrivate.callInstallListeners("onInstallEnded", - self.listeners, self.wrapper, - createWrapper(self.addon)); - - if (self.addon.bootstrap) { - if (self.addon.active) { - XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, - self.addon.type, file, "startup", - reason, extraParams); - } - else { - XPIProvider.unloadBootstrapScope(self.addon.id); - } + else { + XPIProvider.unloadBootstrapScope(this.addon.id); } - }); + } } } catch (e) { @@ -5766,13 +5773,6 @@ AddonInternal.prototype = { releaseNotesURI: null, foreignInstall: false, - get isForeignInstall() { - return this.foreignInstall; - }, - set isForeignInstall(aVal) { - this.foreignInstall = aVal; - }, - get selectedLocale() { if (this._selectedLocale) return this._selectedLocale; @@ -5967,10 +5967,7 @@ AddonInternal.prototype = { * A JS object containing the cached metadata */ importMetadata: function AddonInternal_importMetaData(aObj) { - ["syncGUID", "targetApplications", "userDisabled", "softDisabled", - "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", - "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"] - .forEach(function(aProp) { + PENDING_INSTALL_METADATA.forEach(function(aProp) { if (!(aProp in aObj)) return; @@ -5982,77 +5979,6 @@ AddonInternal.prototype = { } }; -/** - * The DBAddonInternal is a special AddonInternal that has been retrieved from - * the database. Add-ons retrieved synchronously only have the basic metadata - * the rest is filled out synchronously when needed. Asynchronously read add-ons - * have all data available. - */ -function DBAddonInternal() { - this.__defineGetter__("targetApplications", function DBA_targetApplicationsGetter() { - delete this.targetApplications; - return this.targetApplications = XPIDatabase._getTargetApplications(this); - }); - - this.__defineGetter__("targetPlatforms", function DBA_targetPlatformsGetter() { - delete this.targetPlatforms; - return this.targetPlatforms = XPIDatabase._getTargetPlatforms(this); - }); - - this.__defineGetter__("locales", function DBA_localesGetter() { - delete this.locales; - return this.locales = XPIDatabase._getLocales(this); - }); - - this.__defineGetter__("defaultLocale", function DBA_defaultLocaleGetter() { - delete this.defaultLocale; - return this.defaultLocale = XPIDatabase._getDefaultLocale(this); - }); - - this.__defineGetter__("pendingUpgrade", function DBA_pendingUpgradeGetter() { - delete this.pendingUpgrade; - for (let install of XPIProvider.installs) { - if (install.state == AddonManager.STATE_INSTALLED && - !(install.addon instanceof DBAddonInternal) && - install.addon.id == this.id && - install.installLocation == this._installLocation) { - return this.pendingUpgrade = install.addon; - } - }; - }); -} - -DBAddonInternal.prototype = { - applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { - let changes = []; - this.targetApplications.forEach(function(aTargetApp) { - aUpdate.targetApplications.forEach(function(aUpdateTarget) { - if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || - Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { - aTargetApp.minVersion = aUpdateTarget.minVersion; - aTargetApp.maxVersion = aUpdateTarget.maxVersion; - changes.push(aUpdateTarget); - } - }); - }); - try { - XPIDatabase.updateTargetApplications(this, changes); - } - catch (e) { - // A failure just means that we discard the compatibility update - ERROR("Failed to update target application info in the database for " + - "add-on " + this.id, e); - return; - } - XPIProvider.updateAddonDisabledState(this); - } -} - -DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; -// Make it accessible to XPIDatabase. -XPIProvider.DBAddonInternal = DBAddonInternal; - - /** * Creates an AddonWrapper for an AddonInternal. * @@ -6304,7 +6230,7 @@ function AddonWrapper(aAddon) { if (aAddon.syncGUID == val) return val; - if (aAddon instanceof DBAddonInternal) + if (aAddon.inDatabase) XPIDatabase.setAddonSyncGUID(aAddon, val); aAddon.syncGUID = val; @@ -6331,7 +6257,7 @@ function AddonWrapper(aAddon) { this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() { let pending = 0; - if (!(aAddon instanceof DBAddonInternal)) { + if (!(aAddon.inDatabase)) { // Add-on is pending install if there is no associated install (shouldn't // happen here) or if the install is in the process of or has successfully // completed the install. If an add-on is pending install then we ignore @@ -6375,7 +6301,7 @@ function AddonWrapper(aAddon) { let permissions = 0; // Add-ons that aren't installed cannot be modified in any way - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) return permissions; if (!aAddon.appDisabled) { @@ -6410,7 +6336,7 @@ function AddonWrapper(aAddon) { if (val == this.userDisabled) return val; - if (aAddon instanceof DBAddonInternal) { + if (aAddon.inDatabase) { if (aAddon.type == "theme" && val) { if (aAddon.internalName == XPIProvider.defaultSkin) throw new Error("Cannot disable the default theme"); @@ -6434,7 +6360,7 @@ function AddonWrapper(aAddon) { if (val == aAddon.softDisabled) return val; - if (aAddon instanceof DBAddonInternal) { + if (aAddon.inDatabase) { // When softDisabling a theme just enable the active theme if (aAddon.type == "theme" && val && !aAddon.userDisabled) { if (aAddon.internalName == XPIProvider.defaultSkin) @@ -6459,7 +6385,7 @@ function AddonWrapper(aAddon) { }; this.uninstall = function AddonWrapper_uninstall() { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Cannot uninstall an add-on that isn't installed"); if (aAddon.pendingUninstall) throw new Error("Add-on is already marked to be uninstalled"); @@ -6467,7 +6393,7 @@ function AddonWrapper(aAddon) { }; this.cancelUninstall = function AddonWrapper_cancelUninstall() { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Cannot cancel uninstall for an add-on that isn't installed"); if (!aAddon.pendingUninstall) throw new Error("Add-on is not marked to be uninstalled"); diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 0bdcab9af571..465b663058c5 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -18,17 +18,21 @@ XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", ["LOG", "WARN", "ERROR"].forEach(function(aName) { - this.__defineGetter__(aName, function logFuncGetter () { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + Object.defineProperty(this, aName, { + get: function logFuncGetter () { + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); - LogManager.getLogger("addons.xpi-utils", this); - return this[aName]; - }) + LogManager.getLogger("addons.xpi-utils", this); + return this[aName]; + }, + configurable: true + }); }, this); const KEY_PROFILEDIR = "ProfD"; const FILE_DATABASE = "extensions.sqlite"; +const FILE_JSON_DB = "extensions.json"; const FILE_OLD_DATABASE = "extensions.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; @@ -72,16 +76,30 @@ const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"]; const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; +// Properties to save in JSON file +const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", + "internalName", "updateURL", "updateKey", "optionsURL", + "optionsType", "aboutURL", "iconURL", "icon64URL", + "defaultLocale", "visible", "active", "userDisabled", + "appDisabled", "pendingUninstall", "descriptor", "installDate", + "updateDate", "applyBackgroundUpdates", "bootstrap", + "skinnable", "size", "sourceURI", "releaseNotesURI", + "softDisabled", "foreignInstall", "hasBinaryComponents", + "strictCompatibility", "locales", "targetApplications", + "targetPlatforms"]; const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; -this.__defineGetter__("gRDF", function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); +Object.defineProperty(this, "gRDF", { + get: function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); + }, + configurable: true }); function EM_R(aProperty) { @@ -174,6 +192,67 @@ AsyncAddonListCallback.prototype = { } }; +/** + * Asynchronously fill in the _repositoryAddon field for one addon + */ +function getRepositoryAddon(aAddon, aCallback) { + if (!aAddon) { + aCallback(aAddon); + return; + } + function completeAddon(aRepositoryAddon) { + aAddon._repositoryAddon = aRepositoryAddon; + aAddon.compatibilityOverrides = aRepositoryAddon ? + aRepositoryAddon.compatibilityOverrides : + null; + aCallback(aAddon); + } + AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); +} + +/** + * A helper method to asynchronously call a function on an array + * of objects, calling a callback when function(x) has been gathered + * for every element of the array. + * WARNING: not currently error-safe; if the async function does not call + * our internal callback for any of the array elements, asyncMap will not + * call the callback parameter. + * + * @param aObjects + * The array of objects to process asynchronously + * @param aMethod + * Function with signature function(object, function aCallback(f_of_object)) + * @param aCallback + * Function with signature f([aMethod(object)]), called when all values + * are available + */ +function asyncMap(aObjects, aMethod, aCallback) { + var resultsPending = aObjects.length; + var results = [] + if (resultsPending == 0) { + aCallback(results); + return; + } + + function asyncMap_gotValue(aIndex, aValue) { + results[aIndex] = aValue; + if (--resultsPending == 0) { + aCallback(results); + } + } + + aObjects.map(function asyncMap_each(aObject, aIndex, aArray) { + try { + aMethod(aObject, function asyncMap_callback(aResult) { + asyncMap_gotValue(aIndex, aResult); + }); + } + catch (e) { + WARN("Async map function failed", e); + asyncMap_gotValue(aIndex, undefined); + } + }); +} /** * A generator to synchronously return result rows from an mozIStorageStatement. @@ -293,6 +372,78 @@ function copyRowProperties(aRow, aProperties, aTarget) { return aTarget; } +/** + * Create a DBAddonInternal from the fields saved in the JSON database + * or loaded into an AddonInternal from an XPI manifest. + * @return a DBAddonInternal populated with the loaded data + */ + +/** + * The DBAddonInternal is a special AddonInternal that has been retrieved from + * the database. The constructor will initialize the DBAddonInternal with a set + * of fields, which could come from either the JSON store or as an + * XPIProvider.AddonInternal created from an addon's manifest + * @constructor + * @param aLoaded + * Addon data fields loaded from JSON or the addon manifest. + */ +function DBAddonInternal(aLoaded) { + copyProperties(aLoaded, PROP_JSON_FIELDS, this); + if (aLoaded._installLocation) { + this._installLocation = aLoaded._installLocation; + this.location = aLoaded._installLocation._name; + } + else if (aLoaded.location) { + this._installLocation = XPIProvider.installLocationsByName[this.location]; + } + this._key = this.location + ":" + this.id; + try { + this._sourceBundle = this._installLocation.getLocationForID(this.id); + } + catch (e) { + // An exception will be thrown if the add-on appears in the database but + // not on disk. In general this should only happen during startup as + // this change is being detected. + } + + Object.defineProperty(this, "pendingUpgrade", { + get: function DBA_pendingUpgradeGetter() { + delete this.pendingUpgrade; + for (let install of XPIProvider.installs) { + if (install.state == AddonManager.STATE_INSTALLED && + !(install.addon.inDatabase) && + install.addon.id == this.id && + install.installLocation == this._installLocation) { + return this.pendingUpgrade = install.addon; + } + }; + }, + configurable: true + }); +} + +DBAddonInternal.prototype = { + applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { + XPIDatabase.beginTransaction(); + this.targetApplications.forEach(function(aTargetApp) { + aUpdate.targetApplications.forEach(function(aUpdateTarget) { + if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || + Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { + aTargetApp.minVersion = aUpdateTarget.minVersion; + aTargetApp.maxVersion = aUpdateTarget.maxVersion; + } + }); + }); + XPIProvider.updateAddonDisabledState(this); + XPIDatabase.commitTransaction(); + }, + get inDatabase() { + return true; + } +} + +DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; + this.XPIDatabase = { // true if the database connection has been opened initialized: false, @@ -305,6 +456,7 @@ this.XPIDatabase = { transactionCount: 0, // The database file dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true), + jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), // Migration data loaded from an old version of the database. migrateData: null, // Active add-on directories loaded from extensions.ini and prefs at startup. @@ -327,30 +479,6 @@ this.XPIDatabase = { _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " + "WHERE locale_id=:id", - addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " + - ":location, :version, :type, :internalName, " + - ":updateURL, :updateKey, :optionsURL, " + - ":optionsType, :aboutURL, " + - ":iconURL, :icon64URL, :locale, :visible, :active, " + - ":userDisabled, :appDisabled, :pendingUninstall, " + - ":descriptor, :installDate, :updateDate, " + - ":applyBackgroundUpdates, :bootstrap, :skinnable, " + - ":size, :sourceURI, :releaseNotesURI, :softDisabled, " + - ":isForeignInstall, :hasBinaryComponents, " + - ":strictCompatibility)", - addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " + - "(:internal_id, :name, :locale)", - addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " + - "homepageURL) VALUES (:name, :description, " + - ":creator, :homepageURL)", - addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " + - ":type, :value)", - addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " + - "(:internal_id, :id, :minVersion, " + - ":maxVersion)", - addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " + - "(:internal_id, :os, :abi)", - clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id", updateAddonActive: "UPDATE addon SET active=:active WHERE " + "internal_id=:internal_id", @@ -413,6 +541,100 @@ this.XPIDatabase = { return this.dbfileExists = aValue; }, + /** + * Converts the current internal state of the XPI addon database to JSON + * and writes it to the user's profile. Synchronous for now, eventually must + * be async, reliable, etc. + */ + writeJSON: function XPIDB_writeJSON() { + // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet + let addons = []; + for (let aKey in this.addonDB) { + addons.push(copyProperties(this.addonDB[aKey], PROP_JSON_FIELDS)); + } + let toSave = { + schemaVersion: DB_SCHEMA, + addons: addons + }; + + let stream = FileUtils.openSafeFileOutputStream(this.jsonFile); + let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. + createInstance(Ci.nsIConverterOutputStream); + try { + converter.init(stream, "UTF-8", 0, 0x0000); + // XXX pretty print the JSON while debugging + converter.writeString(JSON.stringify(toSave, null, 2)); + converter.flush(); + // nsConverterOutputStream doesn't finish() safe output streams on close() + FileUtils.closeSafeFileOutputStream(stream); + converter.close(); + } + catch(e) { + ERROR("Failed to save database to JSON", e); + stream.close(); + } + }, + + /** + * Open and parse the JSON XPI extensions database. + * @return true: the DB was successfully loaded + * false: The DB either needs upgrade or did not exist at all. + * XXX upgrade and errors handled in a following patch + */ + openJSONDatabase: function XPIDB_openJSONDatabase() { + dump("XPIDB_openJSONDatabase\n"); + try { + let data = ""; + let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + fstream.init(this.jsonFile, -1, 0, 0); + cstream.init(fstream, "UTF-8", 0, 0); + let (str = {}) { + let read = 0; + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + } + cstream.close(); + let inputAddons = JSON.parse(data); + // Now do some sanity checks on our JSON db + if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { + // XXX Content of JSON file is bad, need to rebuild from scratch + ERROR("bad JSON file contents"); + delete this.addonDB; + this.addonDB = {}; + return false; + } + if (inputAddons.schemaVersion != DB_SCHEMA) { + // XXX UPGRADE FROM PREVIOUS VERSION OF JSON DB + ERROR("JSON schema upgrade needed"); + return false; + } + // If we got here, we probably have good data + // Make AddonInternal instances from the loaded data and save them + delete this.addonDB; + let addonDB = {} + inputAddons.addons.forEach(function(loadedAddon) { + let newAddon = new DBAddonInternal(loadedAddon); + addonDB[newAddon._key] = newAddon; + }); + this.addonDB = addonDB; + // dump("Finished reading DB: " + this.addonDB.toSource() + "\n"); + return true; + } + catch(e) { + // XXX handle missing JSON database + ERROR("Failed to load XPI JSON data from profile", e); + // XXX for now, start from scratch + delete this.addonDB; + this.addonDB = {}; + return false; + } + }, + /** * Begins a new transaction in the database. Transactions may be nested. Data * written by an inner transaction may be rolled back on its own. Rolling back @@ -423,8 +645,6 @@ this.XPIDatabase = { * when the database is first opened. */ beginTransaction: function XPIDB_beginTransaction() { - if (this.initialized) - this.getStatement("createSavepoint").execute(); this.transactionCount++; }, @@ -438,9 +658,12 @@ this.XPIDatabase = { return; } - if (this.initialized) - this.getStatement("releaseSavepoint").execute(); this.transactionCount--; + + if (this.transactionCount == 0) { + // All our nested transactions are done, write the JSON file + this.writeJSON(); + } }, /** @@ -453,11 +676,8 @@ this.XPIDatabase = { return; } - if (this.initialized) { - this.getStatement("rollbackSavepoint").execute(); - this.getStatement("releaseSavepoint").execute(); - } this.transactionCount--; + // XXX IRVING we don't handle rollback in the JSON store }, /** @@ -494,7 +714,7 @@ this.XPIDatabase = { } catch (e) { ERROR("Failed to open database (2nd attempt)", e); - + // If we have got here there seems to be no way to open the real // database, instead open a temporary memory database so things will // work for this session. @@ -518,18 +738,20 @@ this.XPIDatabase = { * @param aRebuildOnError * A boolean indicating whether add-on information should be loaded * from the install locations if the database needs to be rebuilt. - * @return the migration data from the database if it was an old schema or - * null otherwise. */ openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) { + this.openJSONDatabase(); + this.initialized = true; + return; + // XXX IRVING deal with the migration logic below and in openDatabaseFile... + delete this.connection; if (!aForceOpen && !this.dbfileExists) { this.connection = null; - return {}; + return; } - this.initialized = true; this.migrateData = null; this.connection = this.openDatabaseFile(this.dbfile); @@ -618,11 +840,12 @@ this.XPIDatabase = { }, /** - * A lazy getter for the database connection. + * Lazy getter for the addons database */ - get connection() { - this.openConnection(true); - return this.connection; + get addonDB() { + delete this.addonDB; + this.openJSONDatabase(); + return this.addonDB; }, /** @@ -783,6 +1006,8 @@ this.XPIDatabase = { migrateData[row.location][row.id] = addonData; props.forEach(function(aProp) { + if (aProp == "isForeignInstall") + addonData.foreignInstall = (row[aProp] == 1); if (DB_BOOL_METADATA.indexOf(aProp) != -1) addonData[aProp] = row[aProp] == 1; else @@ -830,11 +1055,6 @@ this.XPIDatabase = { shutdown: function XPIDB_shutdown(aCallback) { LOG("shutdown"); if (this.initialized) { - for each (let stmt in this.statementCache) - stmt.finalize(); - this.statementCache = {}; - this.addonCache = []; - if (this.transactionCount > 0) { ERROR(this.transactionCount + " outstanding transactions, rolling back."); while (this.transactionCount > 0) @@ -843,24 +1063,28 @@ this.XPIDatabase = { // If we are running with an in-memory database then force a new // extensions.ini to be written to disk on the next startup - if (!this.connection.databaseFile) - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + // XXX IRVING special case for if we fail to save extensions.json? + // XXX maybe doesn't need to be at shutdown? + // if (!this.connection.databaseFile) + // Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); this.initialized = false; - let connection = this.connection; - delete this.connection; - // Re-create the connection smart getter to allow the database to be - // re-loaded during testing. - this.__defineGetter__("connection", function connectionGetter() { - this.openConnection(true); - return this.connection; + // Clear out the cached addons data loaded from JSON and recreate + // the getter to allow database re-loads during testing. + delete this.addonDB; + Object.defineProperty(this, "addonDB", { + get: function addonsGetter() { + this.openJSONDatabase(); + return this.addonDB; + }, + configurable: true }); - - connection.asyncClose(function shutdown_asyncClose() { - LOG("Database closed"); + // XXX IRVING removed an async callback when the database was closed + // XXX do we want to keep the ability to async flush extensions.json + // XXX and then call back? + if (aCallback) aCallback(); - }); } else { if (aCallback) @@ -869,433 +1093,7 @@ this.XPIDatabase = { }, /** - * Gets a cached statement or creates a new statement if it doesn't already - * exist. - * - * @param key - * A unique key to reference the statement - * @param aSql - * An optional SQL string to use for the query, otherwise a - * predefined sql string for the key will be used. - * @return a mozIStorageStatement for the passed SQL - */ - getStatement: function XPIDB_getStatement(aKey, aSql) { - if (aKey in this.statementCache) - return this.statementCache[aKey]; - if (!aSql) - aSql = this.statements[aKey]; - - try { - return this.statementCache[aKey] = this.connection.createStatement(aSql); - } - catch (e) { - ERROR("Error creating statement " + aKey + " (" + aSql + ")"); - throw e; - } - }, - - /** - * Creates the schema in the database. - */ - createSchema: function XPIDB_createSchema() { - LOG("Creating database schema"); - this.beginTransaction(); - - // Any errors in here should rollback the transaction - try { - this.connection.createTable("addon", - "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "id TEXT, syncGUID TEXT, " + - "location TEXT, version TEXT, " + - "type TEXT, internalName TEXT, updateURL TEXT, " + - "updateKey TEXT, optionsURL TEXT, " + - "optionsType TEXT, aboutURL TEXT, iconURL TEXT, " + - "icon64URL TEXT, defaultLocale INTEGER, " + - "visible INTEGER, active INTEGER, " + - "userDisabled INTEGER, appDisabled INTEGER, " + - "pendingUninstall INTEGER, descriptor TEXT, " + - "installDate INTEGER, updateDate INTEGER, " + - "applyBackgroundUpdates INTEGER, " + - "bootstrap INTEGER, skinnable INTEGER, " + - "size INTEGER, sourceURI TEXT, " + - "releaseNotesURI TEXT, softDisabled INTEGER, " + - "isForeignInstall INTEGER, " + - "hasBinaryComponents INTEGER, " + - "strictCompatibility INTEGER, " + - "UNIQUE (id, location), " + - "UNIQUE (syncGUID)"); - this.connection.createTable("targetApplication", - "addon_internal_id INTEGER, " + - "id TEXT, minVersion TEXT, maxVersion TEXT, " + - "UNIQUE (addon_internal_id, id)"); - this.connection.createTable("targetPlatform", - "addon_internal_id INTEGER, " + - "os, abi TEXT, " + - "UNIQUE (addon_internal_id, os, abi)"); - this.connection.createTable("addon_locale", - "addon_internal_id INTEGER, "+ - "locale TEXT, locale_id INTEGER, " + - "UNIQUE (addon_internal_id, locale)"); - this.connection.createTable("locale", - "id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "name TEXT, description TEXT, creator TEXT, " + - "homepageURL TEXT"); - this.connection.createTable("locale_strings", - "locale_id INTEGER, type TEXT, value TEXT"); - this.connection.executeSimpleSQL("CREATE INDEX locale_strings_idx ON " + - "locale_strings (locale_id)"); - this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " + - "ON addon BEGIN " + - "DELETE FROM targetApplication WHERE addon_internal_id=old.internal_id; " + - "DELETE FROM targetPlatform WHERE addon_internal_id=old.internal_id; " + - "DELETE FROM addon_locale WHERE addon_internal_id=old.internal_id; " + - "DELETE FROM locale WHERE id=old.defaultLocale; " + - "END"); - this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon_locale AFTER " + - "DELETE ON addon_locale WHEN NOT EXISTS " + - "(SELECT * FROM addon_locale WHERE locale_id=old.locale_id) BEGIN " + - "DELETE FROM locale WHERE id=old.locale_id; " + - "END"); - this.connection.executeSimpleSQL("CREATE TRIGGER delete_locale AFTER " + - "DELETE ON locale BEGIN " + - "DELETE FROM locale_strings WHERE locale_id=old.id; " + - "END"); - this.connection.schemaVersion = DB_SCHEMA; - this.commitTransaction(); - } - catch (e) { - ERROR("Failed to create database schema", e); - logSQLError(this.connection.lastError, this.connection.lastErrorString); - this.rollbackTransaction(); - this.connection.close(); - this.connection = null; - throw e; - } - }, - - /** - * Synchronously reads the multi-value locale strings for a locale - * - * @param aLocale - * The locale object to read into - */ - _readLocaleStrings: function XPIDB__readLocaleStrings(aLocale) { - let stmt = this.getStatement("_readLocaleStrings"); - - stmt.params.id = aLocale.id; - for (let row in resultRows(stmt)) { - if (!(row.type in aLocale)) - aLocale[row.type] = []; - aLocale[row.type].push(row.value); - } - }, - - /** - * Synchronously reads the locales for an add-on - * - * @param aAddon - * The DBAddonInternal to read the locales for - * @return the array of locales - */ - _getLocales: function XPIDB__getLocales(aAddon) { - let stmt = this.getStatement("_getLocales"); - - let locales = []; - stmt.params.internal_id = aAddon._internal_id; - for (let row in resultRows(stmt)) { - let locale = { - id: row.id, - locales: [row.locale] - }; - copyProperties(row, PROP_LOCALE_SINGLE, locale); - locales.push(locale); - } - locales.forEach(function(aLocale) { - this._readLocaleStrings(aLocale); - }, this); - return locales; - }, - - /** - * Synchronously reads the default locale for an add-on - * - * @param aAddon - * The DBAddonInternal to read the default locale for - * @return the default locale for the add-on - * @throws if the database does not contain the default locale information - */ - _getDefaultLocale: function XPIDB__getDefaultLocale(aAddon) { - let stmt = this.getStatement("_getDefaultLocale"); - - stmt.params.id = aAddon._defaultLocale; - if (!stepStatement(stmt)) - throw new Error("Missing default locale for " + aAddon.id); - let locale = copyProperties(stmt.row, PROP_LOCALE_SINGLE); - locale.id = aAddon._defaultLocale; - stmt.reset(); - this._readLocaleStrings(locale); - return locale; - }, - - /** - * Synchronously reads the target application entries for an add-on - * - * @param aAddon - * The DBAddonInternal to read the target applications for - * @return an array of target applications - */ - _getTargetApplications: function XPIDB__getTargetApplications(aAddon) { - let stmt = this.getStatement("_getTargetApplications"); - - stmt.params.internal_id = aAddon._internal_id; - return [copyProperties(row, PROP_TARGETAPP) for each (row in resultRows(stmt))]; - }, - - /** - * Synchronously reads the target platform entries for an add-on - * - * @param aAddon - * The DBAddonInternal to read the target platforms for - * @return an array of target platforms - */ - _getTargetPlatforms: function XPIDB__getTargetPlatforms(aAddon) { - let stmt = this.getStatement("_getTargetPlatforms"); - - stmt.params.internal_id = aAddon._internal_id; - return [copyProperties(row, ["os", "abi"]) for each (row in resultRows(stmt))]; - }, - - /** - * Synchronously makes a DBAddonInternal from a storage row or returns one - * from the cache. - * - * @param aRow - * The storage row to make the DBAddonInternal from - * @return a DBAddonInternal - */ - makeAddonFromRow: function XPIDB_makeAddonFromRow(aRow) { - if (this.addonCache[aRow.internal_id]) { - let addon = this.addonCache[aRow.internal_id].get(); - if (addon) - return addon; - } - - let addon = new XPIProvider.DBAddonInternal(); - addon._internal_id = aRow.internal_id; - addon._installLocation = XPIProvider.installLocationsByName[aRow.location]; - addon._descriptor = aRow.descriptor; - addon._defaultLocale = aRow.defaultLocale; - copyProperties(aRow, PROP_METADATA, addon); - copyProperties(aRow, DB_METADATA, addon); - DB_BOOL_METADATA.forEach(function(aProp) { - addon[aProp] = aRow[aProp] != 0; - }); - try { - addon._sourceBundle = addon._installLocation.getLocationForID(addon.id); - } - catch (e) { - // An exception will be thrown if the add-on appears in the database but - // not on disk. In general this should only happen during startup as - // this change is being detected. - } - - this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon); - return addon; - }, - - /** - * Asynchronously fetches additional metadata for a DBAddonInternal. - * - * @param aAddon - * The DBAddonInternal - * @param aCallback - * The callback to call when the metadata is completely retrieved - */ - fetchAddonMetadata: function XPIDB_fetchAddonMetadata(aAddon) { - function readLocaleStrings(aLocale, aCallback) { - let stmt = XPIDatabase.getStatement("_readLocaleStrings"); - - stmt.params.id = aLocale.id; - stmt.executeAsync({ - handleResult: function readLocaleStrings_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) { - let type = row.getResultByName("type"); - if (!(type in aLocale)) - aLocale[type] = []; - aLocale[type].push(row.getResultByName("value")); - } - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readLocaleStrings_handleCompletion(aReason) { - aCallback(); - } - }); - } - - function readDefaultLocale() { - delete aAddon.defaultLocale; - let stmt = XPIDatabase.getStatement("_getDefaultLocale"); - - stmt.params.id = aAddon._defaultLocale; - stmt.executeAsync({ - handleResult: function readDefaultLocale_handleResult(aResults) { - aAddon.defaultLocale = copyRowProperties(aResults.getNextRow(), - PROP_LOCALE_SINGLE); - aAddon.defaultLocale.id = aAddon._defaultLocale; - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readDefaultLocale_handleCompletion(aReason) { - if (aAddon.defaultLocale) { - readLocaleStrings(aAddon.defaultLocale, readLocales); - } - else { - ERROR("Missing default locale for " + aAddon.id); - readLocales(); - } - } - }); - } - - function readLocales() { - delete aAddon.locales; - aAddon.locales = []; - let stmt = XPIDatabase.getStatement("_getLocales"); - - stmt.params.internal_id = aAddon._internal_id; - stmt.executeAsync({ - handleResult: function readLocales_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) { - let locale = { - id: row.getResultByName("id"), - locales: [row.getResultByName("locale")] - }; - copyRowProperties(row, PROP_LOCALE_SINGLE, locale); - aAddon.locales.push(locale); - } - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readLocales_handleCompletion(aReason) { - let pos = 0; - function readNextLocale() { - if (pos < aAddon.locales.length) - readLocaleStrings(aAddon.locales[pos++], readNextLocale); - else - readTargetApplications(); - } - - readNextLocale(); - } - }); - } - - function readTargetApplications() { - delete aAddon.targetApplications; - aAddon.targetApplications = []; - let stmt = XPIDatabase.getStatement("_getTargetApplications"); - - stmt.params.internal_id = aAddon._internal_id; - stmt.executeAsync({ - handleResult: function readTargetApplications_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) - aAddon.targetApplications.push(copyRowProperties(row, PROP_TARGETAPP)); - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readTargetApplications_handleCompletion(aReason) { - readTargetPlatforms(); - } - }); - } - - function readTargetPlatforms() { - delete aAddon.targetPlatforms; - aAddon.targetPlatforms = []; - let stmt = XPIDatabase.getStatement("_getTargetPlatforms"); - - stmt.params.internal_id = aAddon._internal_id; - stmt.executeAsync({ - handleResult: function readTargetPlatforms_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) - aAddon.targetPlatforms.push(copyRowProperties(row, ["os", "abi"])); - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readTargetPlatforms_handleCompletion(aReason) { - let callbacks = aAddon._pendingCallbacks; - delete aAddon._pendingCallbacks; - callbacks.forEach(function(aCallback) { - aCallback(aAddon); - }); - } - }); - } - - readDefaultLocale(); - }, - - /** - * Synchronously makes a DBAddonInternal from a mozIStorageRow or returns one - * from the cache. - * - * @param aRow - * The mozIStorageRow to make the DBAddonInternal from - * @return a DBAddonInternal - */ - makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) { - let internal_id = aRow.getResultByName("internal_id"); - if (this.addonCache[internal_id]) { - let addon = this.addonCache[internal_id].get(); - if (addon) { - // If metadata is still pending for this instance add our callback to - // the list to be called when complete, otherwise pass the addon to - // our callback - if ("_pendingCallbacks" in addon) - addon._pendingCallbacks.push(aCallback); - else - aCallback(addon); - return; - } - } - - let addon = new XPIProvider.DBAddonInternal(); - addon._internal_id = internal_id; - let location = aRow.getResultByName("location"); - addon._installLocation = XPIProvider.installLocationsByName[location]; - addon._descriptor = aRow.getResultByName("descriptor"); - copyRowProperties(aRow, PROP_METADATA, addon); - addon._defaultLocale = aRow.getResultByName("defaultLocale"); - copyRowProperties(aRow, DB_METADATA, addon); - DB_BOOL_METADATA.forEach(function(aProp) { - addon[aProp] = aRow.getResultByName(aProp) != 0; - }); - try { - addon._sourceBundle = addon._installLocation.getLocationForID(addon.id); - } - catch (e) { - // An exception will be thrown if the add-on appears in the database but - // not on disk. In general this should only happen during startup as - // this change is being detected. - } - - this.addonCache[internal_id] = Components.utils.getWeakReference(addon); - addon._pendingCallbacks = [aCallback]; - this.fetchAddonMetadata(addon); - }, - - /** - * Synchronously reads all install locations known about by the database. This + * Return a list of all install locations known about by the database. This * is often a a subset of the total install locations when not all have * installed add-ons, occasionally a superset when an install location no * longer exists. @@ -1303,34 +1101,74 @@ this.XPIDatabase = { * @return an array of names of install locations */ getInstallLocations: function XPIDB_getInstallLocations() { - if (!this.connection) + if (!this.addonDB) return []; - let stmt = this.getStatement("getInstallLocations"); + let locations = {}; + for each (let addon in this.addonDB) { + locations[addon.location] = 1; + } + return Object.keys(locations); + }, - return [row.location for each (row in resultRows(stmt))]; + /** + * List all addons that match the filter function + * @param aFilter + * Function that takes an addon instance and returns + * true if that addon should be included in the selected array + * @return an array of DBAddonInternals + */ + _listAddons: function XPIDB_listAddons(aFilter) { + if (!this.addonDB) + return []; + + let addonList = []; + for (let key in this.addonDB) { + let addon = this.addonDB[key]; + if (aFilter(addon)) { + addonList.push(addon); + } + } + + return addonList; + }, + + /** + * Find the first addon that matches the filter function + * @param aFilter + * Function that takes an addon instance and returns + * true if that addon should be selected + * @return The first DBAddonInternal for which the filter returns true + */ + _findAddon: function XPIDB_findAddon(aFilter) { + if (!this.addonDB) + return null; + + for (let key in this.addonDB) { + let addon = this.addonDB[key]; + if (aFilter(addon)) { + return addon; + } + } + + return null; }, /** * Synchronously reads all the add-ons in a particular install location. * - * @param location + * @param aLocation * The name of the install location * @return an array of DBAddonInternals */ getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddonsInLocation"); - - stmt.params.location = aLocation; - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + return this._listAddons(function inLocation(aAddon) {return (aAddon.location == aLocation);}); }, /** * Asynchronously gets an add-on with a particular ID in a particular * install location. + * XXX IRVING sync for now * * @param aId * The ID of the add-on to retrieve @@ -1340,30 +1178,12 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { - if (!this.connection) { - aCallback(null); - return; - } - - let stmt = this.getStatement("getAddonInLocation"); - - stmt.params.id = aId; - stmt.params.location = aLocation; - stmt.executeAsync(new AsyncAddonListCallback(function getAddonInLocation_executeAsync(aAddons) { - if (aAddons.length == 0) { - aCallback(null); - return; - } - // This should never happen but indicates invalid data in the database if - // it does - if (aAddons.length > 1) - ERROR("Multiple addons with ID " + aId + " found in location " + aLocation); - aCallback(aAddons[0]); - })); + getRepositoryAddon(this.addonDB[aLocation + ":" + aId], aCallback); }, /** * Asynchronously gets the add-on with an ID that is visible. + * XXX IRVING sync * * @param aId * The ID of the add-on to retrieve @@ -1371,29 +1191,13 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) { - if (!this.connection) { - aCallback(null); - return; - } - - let stmt = this.getStatement("getVisibleAddonForID"); - - stmt.params.id = aId; - stmt.executeAsync(new AsyncAddonListCallback(function getVisibleAddonForID_executeAsync(aAddons) { - if (aAddons.length == 0) { - aCallback(null); - return; - } - // This should never happen but indicates invalid data in the database if - // it does - if (aAddons.length > 1) - ERROR("Multiple visible addons with ID " + aId + " found"); - aCallback(aAddons[0]); - })); + let addon = this._findAddon(function visibleID(aAddon) {return ((aAddon.id == aId) && aAddon.visible)}); + getRepositoryAddon(addon, aCallback); }, /** * Asynchronously gets the visible add-ons, optionally restricting by type. + * XXX IRVING sync * * @param aTypes * An array of types to include or null to include all types @@ -1401,32 +1205,10 @@ this.XPIDatabase = { * A callback to pass the array of DBAddonInternals to */ getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) { - if (!this.connection) { - aCallback([]); - return; - } - - let stmt = null; - if (!aTypes || aTypes.length == 0) { - stmt = this.getStatement("getVisibleAddons"); - } - else { - let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " + - "type IN ("; - for (let i = 1; i <= aTypes.length; i++) { - sql += "?" + i; - if (i < aTypes.length) - sql += ","; - } - sql += ")"; - - // Note that binding to index 0 sets the value for the ?1 parameter - stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql); - for (let i = 0; i < aTypes.length; i++) - stmt.bindByIndex(i, aTypes[i]); - } - - stmt.executeAsync(new AsyncAddonListCallback(aCallback)); + let addons = this._listAddons(function visibleType(aAddon) { + return (aAddon.visible && (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) + }); + asyncMap(addons, getRepositoryAddon, aCallback); }, /** @@ -1437,13 +1219,7 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddonsByType: function XPIDB_getAddonsByType(aType) { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddonsByType"); - - stmt.params.type = aType; - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + return this._listAddons(function byType(aAddon) { return aAddon.type == aType; }); }, /** @@ -1454,23 +1230,14 @@ this.XPIDatabase = { * @return a DBAddonInternal */ getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) { - if (!this.connection) - return null; - - let stmt = this.getStatement("getVisibleAddonForInternalName"); - - let addon = null; - stmt.params.internalName = aInternalName; - - if (stepStatement(stmt)) - addon = this.makeAddonFromRow(stmt.row); - - stmt.reset(); - return addon; + return this._findAddon(function visibleInternalName(aAddon) { + return (aAddon.visible && (aAddon.internalName == aInternalName)); + }); }, /** * Asynchronously gets all add-ons with pending operations. + * XXX IRVING sync * * @param aTypes * The types of add-ons to retrieve or null to get all types @@ -1479,38 +1246,22 @@ this.XPIDatabase = { */ getVisibleAddonsWithPendingOperations: function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) { - if (!this.connection) { - aCallback([]); - return; - } - let stmt = null; - if (!aTypes || aTypes.length == 0) { - stmt = this.getStatement("getVisibleAddonsWithPendingOperations"); - } - else { - let sql = "SELECT * FROM addon WHERE visible=1 AND " + - "(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " + - "AND type IN ("; - for (let i = 1; i <= aTypes.length; i++) { - sql += "?" + i; - if (i < aTypes.length) - sql += ","; - } - sql += ")"; - - // Note that binding to index 0 sets the value for the ?1 parameter - stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" + - aTypes.length, sql); - for (let i = 0; i < aTypes.length; i++) - stmt.bindByIndex(i, aTypes[i]); - } - - stmt.executeAsync(new AsyncAddonListCallback(aCallback)); + let addons = this._listAddons(function visibleType(aAddon) { + return (aAddon.visible && + (aAddon.pendingUninstall || + // Logic here is tricky. If we're active but either + // disabled flag is set, we're pending disable; if we're not + // active and neither disabled flag is set, we're pending enable + (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && + (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) + }); + asyncMap(addons, getRepositoryAddon, aCallback); }, /** * Asynchronously get an add-on by its Sync GUID. + * XXX IRVING sync * * @param aGUID * Sync GUID of add-on to fetch @@ -1520,16 +1271,8 @@ this.XPIDatabase = { * */ getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) { - let stmt = this.getStatement("getAddonBySyncGUID"); - stmt.params.syncGUID = aGUID; - - stmt.executeAsync(new AsyncAddonListCallback(function getAddonBySyncGUID_executeAsync(aAddons) { - if (aAddons.length == 0) { - aCallback(null); - return; - } - aCallback(aAddons[0]); - })); + let addon = this._findAddon(function bySyncGUID(aAddon) { return aAddon.syncGUID == aGUID; }); + getRepositoryAddon(addon, aCallback); }, /** @@ -1538,12 +1281,7 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddons: function XPIDB_getAddons() { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddons"); - - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + return this._listAddons(function(aAddon) {return true;}); }, /** @@ -1553,92 +1291,27 @@ this.XPIDatabase = { * AddonInternal to add * @param aDescriptor * The file descriptor of the add-on + * @return The DBAddonInternal that was added to the database */ addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) { // If there is no DB yet then forcibly create one - if (!this.connection) + // XXX IRVING I don't think this will work as expected because the addonDB + // getter will kick in. Might not matter because of the way the new DB + // creates itself. + if (!this.addonDB) this.openConnection(false, true); this.beginTransaction(); - var self = this; - function insertLocale(aLocale) { - let localestmt = self.getStatement("addAddonMetadata_locale"); - let stringstmt = self.getStatement("addAddonMetadata_strings"); - - copyProperties(aLocale, PROP_LOCALE_SINGLE, localestmt.params); - executeStatement(localestmt); - let row = XPIDatabase.connection.lastInsertRowID; - - PROP_LOCALE_MULTI.forEach(function(aProp) { - aLocale[aProp].forEach(function(aStr) { - stringstmt.params.locale = row; - stringstmt.params.type = aProp; - stringstmt.params.value = aStr; - executeStatement(stringstmt); - }); - }); - return row; + let newAddon = new DBAddonInternal(aAddon); + newAddon.descriptor = aDescriptor; + this.addonDB[newAddon._key] = newAddon; + if (newAddon.visible) { + this.makeAddonVisible(newAddon); } - // Any errors in here should rollback the transaction - try { - - if (aAddon.visible) { - let stmt = this.getStatement("clearVisibleAddons"); - stmt.params.id = aAddon.id; - executeStatement(stmt); - } - - let stmt = this.getStatement("addAddonMetadata_addon"); - - stmt.params.locale = insertLocale(aAddon.defaultLocale); - stmt.params.location = aAddon._installLocation.name; - stmt.params.descriptor = aDescriptor; - copyProperties(aAddon, PROP_METADATA, stmt.params); - copyProperties(aAddon, DB_METADATA, stmt.params); - DB_BOOL_METADATA.forEach(function(aProp) { - stmt.params[aProp] = aAddon[aProp] ? 1 : 0; - }); - executeStatement(stmt); - let internal_id = this.connection.lastInsertRowID; - - stmt = this.getStatement("addAddonMetadata_addon_locale"); - aAddon.locales.forEach(function(aLocale) { - let id = insertLocale(aLocale); - aLocale.locales.forEach(function(aName) { - stmt.params.internal_id = internal_id; - stmt.params.name = aName; - stmt.params.locale = id; - executeStatement(stmt); - }); - }); - - stmt = this.getStatement("addAddonMetadata_targetApplication"); - - aAddon.targetApplications.forEach(function(aApp) { - stmt.params.internal_id = internal_id; - stmt.params.id = aApp.id; - stmt.params.minVersion = aApp.minVersion; - stmt.params.maxVersion = aApp.maxVersion; - executeStatement(stmt); - }); - - stmt = this.getStatement("addAddonMetadata_targetPlatform"); - - aAddon.targetPlatforms.forEach(function(aPlatform) { - stmt.params.internal_id = internal_id; - stmt.params.os = aPlatform.os; - stmt.params.abi = aPlatform.abi; - executeStatement(stmt); - }); - - this.commitTransaction(); - } - catch (e) { - this.rollbackTransaction(); - throw e; - } + this.commitTransaction(); + return newAddon; }, /** @@ -1651,6 +1324,7 @@ this.XPIDatabase = { * The new AddonInternal to add * @param aDescriptor * The file descriptor of the add-on + * @return The DBAddonInternal that was added to the database */ updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon, aDescriptor) { @@ -1666,38 +1340,9 @@ this.XPIDatabase = { aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && !aNewAddon.appDisabled && !aNewAddon.pendingUninstall) - this.addAddonMetadata(aNewAddon, aDescriptor); - this.commitTransaction(); - } - catch (e) { - this.rollbackTransaction(); - throw e; - } - }, - - /** - * Synchronously updates the target application entries for an add-on. - * - * @param aAddon - * The DBAddonInternal being updated - * @param aTargets - * The array of target applications to update - */ - updateTargetApplications: function XPIDB_updateTargetApplications(aAddon, - aTargets) { - this.beginTransaction(); - - // Any errors in here should rollback the transaction - try { - let stmt = this.getStatement("updateTargetApplications"); - aTargets.forEach(function(aTarget) { - stmt.params.internal_id = aAddon._internal_id; - stmt.params.id = aTarget.id; - stmt.params.minVersion = aTarget.minVersion; - stmt.params.maxVersion = aTarget.maxVersion; - executeStatement(stmt); - }); + let newDBAddon = this.addAddonMetadata(aNewAddon, aDescriptor); this.commitTransaction(); + return newDBAddon; } catch (e) { this.rollbackTransaction(); @@ -1712,9 +1357,9 @@ this.XPIDatabase = { * The DBAddonInternal being removed */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { - let stmt = this.getStatement("removeAddonMetadata"); - stmt.params.internal_id = aAddon._internal_id; - executeStatement(stmt); + this.beginTransaction(); + delete this.addonDB[aAddon._key]; + this.commitTransaction(); }, /** @@ -1727,15 +1372,17 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { - let stmt = this.getStatement("clearVisibleAddons"); - stmt.params.id = aAddon.id; - executeStatement(stmt); - - stmt = this.getStatement("makeAddonVisible"); - stmt.params.internal_id = aAddon._internal_id; - executeStatement(stmt); - + this.beginTransaction(); + LOG("Make addon " + aAddon._key + " visible"); + for (let key in this.addonDB) { + let otherAddon = this.addonDB[key]; + if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { + LOG("Hide addon " + otherAddon._key); + otherAddon.visible = false; + } + } aAddon.visible = true; + this.commitTransaction(); }, /** @@ -1747,33 +1394,11 @@ this.XPIDatabase = { * A dictionary of properties to set */ setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) { - function convertBoolean(value) { - return value ? 1 : 0; + this.beginTransaction(); + for (let key in aProperties) { + aAddon[key] = aProperties[key]; } - - let stmt = this.getStatement("setAddonProperties"); - stmt.params.internal_id = aAddon._internal_id; - - ["userDisabled", "appDisabled", "softDisabled", - "pendingUninstall"].forEach(function(aProp) { - if (aProp in aProperties) { - stmt.params[aProp] = convertBoolean(aProperties[aProp]); - aAddon[aProp] = aProperties[aProp]; - } - else { - stmt.params[aProp] = convertBoolean(aAddon[aProp]); - } - }); - - if ("applyBackgroundUpdates" in aProperties) { - stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates; - aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates; - } - else { - stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates; - } - - executeStatement(stmt); + this.commitTransaction(); }, /** @@ -1783,29 +1408,36 @@ this.XPIDatabase = { * The DBAddonInternal being updated * @param aGUID * GUID string to set the value to + * @throws if another addon already has the specified GUID */ setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) { - let stmt = this.getStatement("setAddonSyncGUID"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.syncGUID = aGUID; - - executeStatement(stmt); + // Need to make sure no other addon has this GUID + function excludeSyncGUID(otherAddon) { + return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID); + } + let otherAddon = this._findAddon(excludeSyncGUID); + if (otherAddon) { + throw new Error("Addon sync GUID conflict for addon " + aAddon._key + + ": " + otherAddon._key + " already has GUID " + aGUID); + } + this.beginTransaction(); + aAddon.syncGUID = aGUID; + this.commitTransaction(); }, /** * Synchronously sets the file descriptor for an add-on. + * XXX IRVING could replace this with setAddonProperties * * @param aAddon * The DBAddonInternal being updated - * @param aProperties - * A dictionary of properties to set + * @param aDescriptor + * File path of the installed addon */ setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { - let stmt = this.getStatement("setAddonDescriptor"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.descriptor = aDescriptor; - - executeStatement(stmt); + this.beginTransaction(); + aAddon.descriptor = aDescriptor; + this.commitTransaction(); }, /** @@ -1814,28 +1446,29 @@ this.XPIDatabase = { * @param aAddon * The DBAddonInternal to update */ - updateAddonActive: function XPIDB_updateAddonActive(aAddon) { - LOG("Updating add-on state"); + updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) { + LOG("Updating active state for add-on " + aAddon.id + " to " + aActive); - let stmt = this.getStatement("updateAddonActive"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.active = aAddon.active ? 1 : 0; - executeStatement(stmt); + this.beginTransaction(); + aAddon.active = aActive; + this.commitTransaction(); }, /** * Synchronously calculates and updates all the active flags in the database. */ updateActiveAddons: function XPIDB_updateActiveAddons() { + // XXX IRVING this may get called during XPI-utils shutdown + // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); - let stmt = this.getStatement("setActiveAddons"); - executeStatement(stmt); - - // Note that this does not update the active property on cached - // DBAddonInternal instances so we throw away the cache. This should only - // happen during shutdown when everything is going away anyway or during - // startup when the only references are internal. - this.addonCache = []; + this.beginTransaction(); + for (let key in this.addonDB) { + let addon = this.addonDB[key]; + addon.active = (addon.visible && !addon.userDisabled && + !addon.softDisabled && !addon.appDisabled && + !addon.pendingUninstall); + } + this.commitTransaction(); }, /** @@ -1846,26 +1479,16 @@ this.XPIDatabase = { let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], true); - if (!this.connection) { - try { - addonsList.remove(false); - LOG("Deleted add-ons list"); - } - catch (e) { - } - - Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS); - return; - } - let enabledAddons = []; let text = "[ExtensionDirs]\r\n"; let count = 0; let fullCount = 0; - let stmt = this.getStatement("getActiveAddons"); + let activeAddons = this._listAddons(function active(aAddon) { + return aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme"); + }); - for (let row in resultRows(stmt)) { + for (let row of activeAddons) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); @@ -1881,17 +1504,22 @@ this.XPIDatabase = { dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED); } catch (e) {} + let themes = []; if (dssEnabled) { - stmt = this.getStatement("getThemes"); + themes = this._listAddons(function isTheme(aAddon){ return aAddon.type == "theme"; }); } else { - stmt = this.getStatement("getActiveTheme"); - stmt.params.internalName = XPIProvider.selectedSkin; + let activeTheme = this._findAddon(function isSelected(aAddon) { + return ((aAddon.type == "theme") && (aAddon.internalName == XPIProvider.selectedSkin)); + }); + if (activeTheme) { + themes.push(activeTheme); + } } - if (stmt) { + if (themes.length > 0) { count = 0; - for (let row in resultRows(stmt)) { + for (let row of themes) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index b7bc65ddc14f..e0c34ee1aa35 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1394,6 +1394,60 @@ function do_exception_wrap(func) { }; } +const EXTENSIONS_DB = "extensions.json"; + +/** + * Change the schema version of the JSON extensions database + */ +function changeXPIDBVersion(aNewVersion) { + let dbfile = gProfD.clone(); + dbfile.append(EXTENSIONS_DB); + let jData = loadJSON(dbfile); + jData.schemaVersion = aNewVersion; + saveJSON(jData, dbfile); +} + +/** + * Raw load of a JSON file + */ +function loadJSON(aFile) { + let data = ""; + let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + fstream.init(aFile, -1, 0, 0); + cstream.init(fstream, "UTF-8", 0, 0); + let (str = {}) { + let read = 0; + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + } + cstream.close(); + do_print("Loaded JSON file " + aFile.spec); + return(JSON.parse(data)); +} + +/** + * Raw save of a JSON blob to file + */ +function saveJSON(aData, aFile) { + do_print("Starting to save JSON file " + aFile.path); + let stream = FileUtils.openSafeFileOutputStream(aFile); + let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"]. + createInstance(AM_Ci.nsIConverterOutputStream); + converter.init(stream, "UTF-8", 0, 0x0000); + // XXX pretty print the JSON while debugging + converter.writeString(JSON.stringify(aData, null, 2)); + converter.flush(); + // nsConverterOutputStream doesn't finish() safe output streams on close() + FileUtils.closeSafeFileOutputStream(stream); + converter.close(); + do_print("Done saving JSON file " + aFile.path); +} + /** * Create a callback function that calls do_execute_soon on an actual callback and arguments */ diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js index 1ccdbc842c42..6ebf088d6fdd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js @@ -254,14 +254,11 @@ function run_test_1() { function run_test_1_modified_db() { - // After restarting the database won't be open and so can be replaced with - // a bad file - restartManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + // After restarting the database won't be open so we can alter + // the schema + shutdownManager(); + changeXPIDBVersion(100); + startupManager(); // Accessing the add-ons should open and recover the database. Since // migration occurs everything should be recovered correctly diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 50dcb427759d..d3b8aceb2ccb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -531,10 +531,10 @@ function manual_update(aVersion, aCallback) { // Checks that an add-ons properties match expected values function check_addon(aAddon, aExpectedVersion, aExpectedUserDisabled, aExpectedSoftDisabled, aExpectedState) { + do_check_neq(aAddon, null); dump("Testing " + aAddon.id + " version " + aAddon.version + "\n"); dump(aAddon.userDisabled + " " + aAddon.softDisabled + "\n"); - do_check_neq(aAddon, null); do_check_eq(aAddon.version, aExpectedVersion); do_check_eq(aAddon.blocklistState, aExpectedState); do_check_eq(aAddon.userDisabled, aExpectedUserDisabled); @@ -706,11 +706,7 @@ add_test(function run_app_update_schema_test() { function update_schema_2() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "2"; startupManager(true); @@ -738,11 +734,7 @@ add_test(function run_app_update_schema_test() { restartManager(); shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "2.5"; startupManager(true); @@ -764,11 +756,7 @@ add_test(function run_app_update_schema_test() { function update_schema_4() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); startupManager(false); AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { @@ -789,11 +777,7 @@ add_test(function run_app_update_schema_test() { function update_schema_5() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "1"; startupManager(true); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index bd51971a0345..6d201640f989 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -11,8 +11,6 @@ const ADDON_UNINSTALL = 6; const ADDON_UPGRADE = 7; const ADDON_DOWNGRADE = 8; -const EXTENSIONS_DB = "extensions.sqlite"; - // This verifies that bootstrappable add-ons can be used without restarts. Components.utils.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index 4987eb51d4c8..789819cc6b87 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -5,8 +5,6 @@ // This verifies that deleting the database from the profile doesn't break // anything -const EXTENSIONS_DB = "extensions.sqlite"; - const profileDir = gProfD.clone(); profileDir.append("extensions"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index ea2e18a3698b..92ae8b21dc14 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -108,14 +108,8 @@ function run_test_1() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); @@ -255,14 +249,8 @@ function run_test_2() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js index 5cce5de71412..50dd782da360 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -252,7 +252,7 @@ function run_test_1() { // because there is a file there still. shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); + dbfile.append("extensions.json"); dbfile.remove(true); dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js index e768e2798533..e465b47bbacb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js @@ -253,7 +253,7 @@ function run_test_1() { // because there is a file there still. shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); + dbfile.append("extensions.json"); dbfile.remove(true); dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js b/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js deleted file mode 100644 index 693ca42cda98..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js +++ /dev/null @@ -1,181 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -// This tests the data in extensions.sqlite for general sanity, making sure -// rows in one table only reference rows in another table that actually exist. - - -function check_db() { - do_print("Checking DB sanity..."); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - - do_print("Checking locale_strings references rows in locale correctly..."); - let localeStringsStmt = db.createStatement("SELECT * FROM locale_strings"); - let localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); - let i = 0; - while (localeStringsStmt.executeStep()) { - i++; - localeStmt.params.locale_id = localeStringsStmt.row.locale_id; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - localeStmt.finalize(); - localeStringsStmt.finalize(); - do_print("Done. " + i + " rows in locale_strings checked."); - - - do_print("Checking locale references rows in addon_locale and addon correctly..."); - localeStmt = db.createStatement("SELECT * FROM locale"); - let addonLocaleStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon_locale WHERE locale_id=:locale_id"); - let addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE defaultLocale=:locale_id"); - i = 0; - while (localeStmt.executeStep()) { - i++; - addonLocaleStmt.params.locale_id = localeStmt.row.id; - do_check_true(addonLocaleStmt.executeStep()); - if (addonLocaleStmt.row.count == 0) { - addonStmt.params.locale_id = localeStmt.row.id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - } else { - do_check_eq(addonLocaleStmt.row.count, 1); - } - addonLocaleStmt.reset(); - addonStmt.reset(); - } - addonLocaleStmt.finalize(); - localeStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in locale checked."); - - - do_print("Checking addon_locale references rows in locale correctly..."); - addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); - localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); - i = 0; - while (addonLocaleStmt.executeStep()) { - i++; - localeStmt.params.locale_id = addonLocaleStmt.row.locale_id; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - addonLocaleStmt.finalize(); - localeStmt.finalize(); - do_print("Done. " + i + " rows in addon_locale checked."); - - - do_print("Checking addon_locale references rows in addon correctly..."); - addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (addonLocaleStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = addonLocaleStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - addonLocaleStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in addon_locale checked."); - - - do_print("Checking addon references rows in locale correctly..."); - addonStmt = db.createStatement("SELECT * FROM addon"); - localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:defaultLocale"); - i = 0; - while (addonStmt.executeStep()) { - i++; - localeStmt.params.defaultLocale = addonStmt.row.defaultLocale; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - addonStmt.finalize(); - localeStmt.finalize(); - do_print("Done. " + i + " rows in addon checked."); - - - do_print("Checking targetApplication references rows in addon correctly..."); - let targetAppStmt = db.createStatement("SELECT * FROM targetApplication"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (targetAppStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = targetAppStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - targetAppStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in targetApplication checked."); - - - do_print("Checking targetPlatform references rows in addon correctly..."); - let targetPlatformStmt = db.createStatement("SELECT * FROM targetPlatform"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (targetPlatformStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = targetPlatformStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - targetPlatformStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in targetPlatform checked."); - - - db.close(); - do_print("Done checking DB sanity."); -} - -function run_test() { - do_test_pending(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); - startupManager(); - - installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_1); -} - -function run_test_1() { - shutdownManager(); - check_db(); - startupManager(); - - AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { - aAddon.uninstall(); - - shutdownManager(); - check_db(); - startupManager(); - - installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_2); - }); -} - -function run_test_2() { - installAllFiles([do_get_addon("test_db_sanity_1_2")], function() { - shutdownManager(); - check_db(); - startupManager(); - run_test_3(); - }); -} - -function run_test_3() { - AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { - aAddon.uninstall(); - - shutdownManager(); - check_db(); - - do_test_finished(); - }); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js index 7c9d3dc96dbb..883ea08c6d1f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -254,16 +254,13 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // After shutting down the database won't be open so we can lock it + // After shutting down the database won't be open so we can + // mess with permissions shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let connection = Services.storage.openUnsharedDatabase(dbfile); - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - // Force the DB to become locked - connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); - connection.commitTransaction(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -431,7 +428,7 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - connection.close(); + dbfile.permissions = savedPermissions; // After allowing access to the original DB things should go back to as // they were previously diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 878e088e69a7..6e21df540d1f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -145,13 +145,9 @@ function run_test() { // After shutting down the database won't be open so we can lock it shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let connection = Services.storage.openUnsharedDatabase(dbfile); - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - // Force the DB to become locked - connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); - connection.commitTransaction(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -203,7 +199,7 @@ function run_test() { do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - connection.close(); + dbfile.permissions = savedPermissions; // After allowing access to the original DB things should still be // applied correctly diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index 0585295d26d8..2c31171318be 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -257,13 +257,9 @@ function run_test_1() { // After shutting down the database won't be open so we can lock it shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let connection = Services.storage.openUnsharedDatabase(dbfile); - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - // Force the DB to become locked - connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); - connection.commitTransaction(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -429,7 +425,7 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - connection.close(); + dbfile.permissions = savedPermissions; // After allowing access to the original DB things should go back to as // they were previously diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js index b14e332235f0..20431a9a7d00 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -224,7 +224,7 @@ function run_test() { do_check_true(a4.isActive); do_check_true(a4.strictCompatibility); do_check_false(a4.foreignInstall); - // addon5 was enabled in the database but needed a compatibiltiy update + // addon5 was enabled in the database but needed a compatibility update do_check_neq(a5, null); do_check_false(a5.userDisabled); do_check_false(a5.appDisabled); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js index c8345d8c9a3d..b2903ead7d54 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we migrate data from a previous version of the sqlite database +// Checks that we migrate data from a previous version of the JSON database // The test extension uses an insecure update url. Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); @@ -172,14 +172,8 @@ function perform_migration() { // Turn on disabling for all scopes Services.prefs.setIntPref("extensions.autoDisableScopes", 15); - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); gAppInfo.version = "2" startupManager(true); @@ -247,7 +241,7 @@ function test_results() { do_check_false(a4.hasBinaryComponents); do_check_true(a4.strictCompatibility); - // addon5 was enabled in the database but needed a compatibiltiy update + // addon5 was enabled in the database but needed a compatibility update do_check_neq(a5, null); do_check_false(a5.userDisabled); do_check_false(a5.appDisabled); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js index 323ede3114f5..91a05fa358e3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we fail to migrate but still start up ok when there is a database +// Checks that we fail to migrate but still start up ok when there is a SQLITE database // with no useful data in it. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index 7e25e836c9c8..841a50224169 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -133,7 +133,7 @@ function run_test() { check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []); let file = gProfD.clone(); - file.append("extensions.sqlite"); + file.append("extensions.json"); do_check_false(file.exists()); file.leafName = "extensions.ini"; @@ -191,7 +191,7 @@ function run_test_1() { do_check_true(gCachePurged); let file = gProfD.clone(); - file.append("extensions.sqlite"); + file.append("extensions.json"); do_check_true(file.exists()); file.leafName = "extensions.ini"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js index 34cd54039f34..c73a412c4870 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js @@ -94,8 +94,7 @@ add_test(function test_error_on_duplicate_syncguid_insert() { do_throw("Should not get here."); } catch (e) { - do_check_eq(e.result, - Components.results.NS_ERROR_STORAGE_CONSTRAINT); + do_check_true(e.message.startsWith("Addon sync GUID conflict")); restartManager(); AddonManager.getAddonByID(installIDs[1], function(addon) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 81f0eddf08a0..49fef590e1c0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -17,7 +17,11 @@ skip-if = os == "android" [test_LightweightThemeManager.js] [test_backgroundupdate.js] [test_badschema.js] +# Needs rewrite for JSON XPIDB +fail-if = true [test_blocklistchange.js] +# Needs rewrite for JSON XPIDB +fail-if = true # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_blocklist_regexp.js] @@ -138,6 +142,8 @@ fail-if = os == "android" [test_bug620837.js] [test_bug655254.js] [test_bug659772.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_bug675371.js] [test_bug740612.js] [test_bug753900.js] @@ -147,8 +153,11 @@ fail-if = os == "android" [test_ChromeManifestParser.js] [test_compatoverrides.js] [test_corrupt.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_corrupt_strictcompat.js] -[test_db_sanity.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_dictionary.js] [test_langpack.js] [test_disable.js] @@ -193,17 +202,33 @@ skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. [test_locale.js] [test_locked.js] +# Needs sqlite->JSON conversion +fail-if = true [test_locked2.js] +# Needs sqlite->JSON conversion +fail-if = true [test_locked_strictcompat.js] +# Needs sqlite->JSON conversion +fail-if = true [test_manifest.js] [test_mapURIToAddonID.js] # Same as test_bootstrap.js skip-if = os == "android" [test_migrate1.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate2.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate3.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate4.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate5.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrateAddonRepository.js] [test_onPropertyChanged_appDisabled.js] [test_permissions.js] From e34471c4c9d46d55f3e559c403d13c73374a54d9 Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Thu, 8 Aug 2013 15:56:26 -0400 Subject: [PATCH 12/40] Bug 853388: Upgrade existing SQLITE databases to JSON; r=unfocused --- toolkit/mozapps/extensions/XPIProvider.jsm | 30 +- .../mozapps/extensions/XPIProviderUtils.js | 609 +++++++----------- .../extensions/test/xpcshell/head_addons.js | 10 +- .../extensions/test/xpcshell/test_bad_json.js | 54 ++ .../test/xpcshell/test_bootstrap.js | 10 +- .../test/xpcshell/test_bug559800.js | 10 +- .../extensions/test/xpcshell/test_corrupt.js | 6 +- .../xpcshell/test_corrupt_strictcompat.js | 6 +- .../extensions/test/xpcshell/test_locked.js | 13 +- .../extensions/test/xpcshell/test_locked2.js | 12 +- .../test/xpcshell/test_locked_strictcompat.js | 12 +- .../extensions/test/xpcshell/xpcshell.ini | 27 +- 12 files changed, 320 insertions(+), 479 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index ca763ec78741..2794fd309749 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -595,14 +595,8 @@ function isAddonDisabled(aAddon) { return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled; } -Object.defineProperty(this, "gRDF", { - get: function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); - }, - configurable: true -}); +XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1", + Ci.nsIRDFService); function EM_R(aProperty) { return gRDF.GetResource(PREFIX_NS_EM + aProperty); @@ -1765,7 +1759,7 @@ var XPIProvider = { null); this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); - this.enabledAddons = []; + this.enabledAddons = ""; Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false); Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false); @@ -2904,6 +2898,7 @@ var XPIProvider = { let newDBAddon = null; try { // Update the database. + // XXX I don't think this can throw any more newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); } catch (e) { @@ -3005,9 +3000,8 @@ var XPIProvider = { let addonStates = aSt.addons; // Check if the database knows about any add-ons in this install location. - let pos = knownLocations.indexOf(installLocation.name); - if (pos >= 0) { - knownLocations.splice(pos, 1); + if (knownLocations.has(installLocation.name)) { + knownLocations.delete(installLocation.name); let addons = XPIDatabase.getAddonsInLocation(installLocation.name); // Iterate through the add-ons installed the last time the application // ran @@ -3084,12 +3078,12 @@ var XPIProvider = { // have any add-ons installed in them, or the locations no longer exist. // The metadata for the add-ons that were in them must be removed from the // database. - knownLocations.forEach(function(aLocation) { - let addons = XPIDatabase.getAddonsInLocation(aLocation); + for (let location of knownLocations) { + let addons = XPIDatabase.getAddonsInLocation(location); addons.forEach(function(aOldAddon) { changed = removeMetadata(aOldAddon) || changed; }, this); - }, this); + } // Tell Telemetry what we found AddonManagerPrivate.recordSimpleMeasure("modifiedUnpacked", modifiedUnpacked); @@ -5379,6 +5373,8 @@ AddonInstall.prototype = { reason, extraParams); } else { + // XXX this makes it dangerous to do many things in onInstallEnded + // listeners because important cleanup hasn't been done yet XPIProvider.unloadBootstrapScope(this.addon.id); } } @@ -5756,8 +5752,8 @@ UpdateChecker.prototype = { /** * The AddonInternal is an internal only representation of add-ons. It may - * have come from the database (see DBAddonInternal below) or an install - * manifest. + * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm) + * or an install manifest. */ function AddonInternal() { } diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 465b663058c5..87c0d910f014 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -16,7 +16,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm"); - ["LOG", "WARN", "ERROR"].forEach(function(aName) { Object.defineProperty(this, aName, { get: function logFuncGetter () { @@ -93,14 +92,8 @@ const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; -Object.defineProperty(this, "gRDF", { - get: function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); - }, - configurable: true -}); +XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1", + Ci.nsIRDFService); function EM_R(aProperty) { return gRDF.GetResource(PREFIX_NS_EM + aProperty); @@ -138,60 +131,6 @@ function getRDFProperty(aDs, aResource, aProperty) { return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true)); } - -/** - * A mozIStorageStatementCallback that will asynchronously build DBAddonInternal - * instances from the results it receives. Once the statement has completed - * executing and all of the metadata for all of the add-ons has been retrieved - * they will be passed as an array to aCallback. - * - * @param aCallback - * A callback function to pass the array of DBAddonInternals to - */ -function AsyncAddonListCallback(aCallback) { - this.callback = aCallback; - this.addons = []; -} - -AsyncAddonListCallback.prototype = { - callback: null, - complete: false, - count: 0, - addons: null, - - handleResult: function AsyncAddonListCallback_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) { - this.count++; - let self = this; - XPIDatabase.makeAddonFromRowAsync(row, function handleResult_makeAddonFromRowAsync(aAddon) { - function completeAddon(aRepositoryAddon) { - aAddon._repositoryAddon = aRepositoryAddon; - aAddon.compatibilityOverrides = aRepositoryAddon ? - aRepositoryAddon.compatibilityOverrides : - null; - self.addons.push(aAddon); - if (self.complete && self.addons.length == self.count) - self.callback(self.addons); - } - - if ("getCachedAddonByID" in AddonRepository) - AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); - else - completeAddon(null); - }); - } - }, - - handleError: asyncErrorLogger, - - handleCompletion: function AsyncAddonListCallback_handleCompletion(aReason) { - this.complete = true; - if (this.addons.length == this.count) - this.callback(this.addons); - } -}; - /** * Asynchronously fill in the _repositoryAddon field for one addon */ @@ -293,24 +232,6 @@ function asyncErrorLogger(aError) { logSQLError(aError.result, aError.message); } -/** - * A helper function to execute a statement synchronously and log any error - * that occurs. - * - * @param aStatement - * A mozIStorageStatement to execute - */ -function executeStatement(aStatement) { - try { - aStatement.execute(); - } - catch (e) { - logSQLError(XPIDatabase.connection.lastError, - XPIDatabase.connection.lastErrorString); - throw e; - } -} - /** * A helper function to step a statement synchronously and log any error that * occurs. @@ -372,12 +293,6 @@ function copyRowProperties(aRow, aProperties, aTarget) { return aTarget; } -/** - * Create a DBAddonInternal from the fields saved in the JSON database - * or loaded into an AddonInternal from an XPI manifest. - * @return a DBAddonInternal populated with the loaded data - */ - /** * The DBAddonInternal is a special AddonInternal that has been retrieved from * the database. The constructor will initialize the DBAddonInternal with a set @@ -389,6 +304,7 @@ function copyRowProperties(aRow, aProperties, aTarget) { */ function DBAddonInternal(aLoaded) { copyProperties(aLoaded, PROP_JSON_FIELDS, this); + if (aLoaded._installLocation) { this._installLocation = aLoaded._installLocation; this.location = aLoaded._installLocation._name; @@ -396,7 +312,9 @@ function DBAddonInternal(aLoaded) { else if (aLoaded.location) { this._installLocation = XPIProvider.installLocationsByName[this.location]; } + this._key = this.location + ":" + this.id; + try { this._sourceBundle = this._installLocation.getLocationForID(this.id); } @@ -406,20 +324,20 @@ function DBAddonInternal(aLoaded) { // this change is being detected. } - Object.defineProperty(this, "pendingUpgrade", { - get: function DBA_pendingUpgradeGetter() { - delete this.pendingUpgrade; + // XXX Can we redesign pendingUpgrade? + XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", + function DBA_pendingUpgradeGetter() { for (let install of XPIProvider.installs) { if (install.state == AddonManager.STATE_INSTALLED && !(install.addon.inDatabase) && install.addon.id == this.id && install.installLocation == this._installLocation) { + delete this.pendingUpgrade; return this.pendingUpgrade = install.addon; } }; - }, - configurable: true - }); + return null; + }); } DBAddonInternal.prototype = { @@ -437,8 +355,13 @@ DBAddonInternal.prototype = { XPIProvider.updateAddonDisabledState(this); XPIDatabase.commitTransaction(); }, + get inDatabase() { return true; + }, + + toJSON: function() { + return copyProperties(this, PROP_JSON_FIELDS); } } @@ -447,94 +370,21 @@ DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; this.XPIDatabase = { // true if the database connection has been opened initialized: false, - // A cache of statements that are used and need to be finalized on shutdown - statementCache: {}, - // A cache of weak referenced DBAddonInternals so we can reuse objects where - // possible - addonCache: [], // The nested transaction count transactionCount: 0, // The database file - dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true), jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), // Migration data loaded from an old version of the database. migrateData: null, // Active add-on directories loaded from extensions.ini and prefs at startup. activeBundles: null, + // Special handling for when the database is locked at first load + lockedDatabase: false, - // The statements used by the database - statements: { - _getDefaultLocale: "SELECT id, name, description, creator, homepageURL " + - "FROM locale WHERE id=:id", - _getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " + - "locale.description, locale.creator, locale.homepageURL " + - "FROM addon_locale JOIN locale ON " + - "addon_locale.locale_id=locale.id WHERE " + - "addon_internal_id=:internal_id", - _getTargetApplications: "SELECT addon_internal_id, id, minVersion, " + - "maxVersion FROM targetApplication WHERE " + - "addon_internal_id=:internal_id", - _getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " + - "addon_internal_id=:internal_id", - _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " + - "WHERE locale_id=:id", - - clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id", - updateAddonActive: "UPDATE addon SET active=:active WHERE " + - "internal_id=:internal_id", - - getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " + - "type<>'theme' AND bootstrap=0", - getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + - "internalName=:internalName AND type='theme'", - getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'", - - getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " + - "AND location=:location", - getAddons: "SELECT " + FIELDS_ADDON + " FROM addon", - getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type", - getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + - "location=:location", - getInstallLocations: "SELECT DISTINCT location FROM addon", - getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + - "visible=1 AND id=:id", - getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " + - "WHERE visible=1 AND internalName=:internalName", - getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1", - getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " + - "addon WHERE visible=1 " + - "AND (pendingUninstall=1 OR " + - "MAX(userDisabled,appDisabled)=active)", - getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " + - "WHERE syncGUID=:syncGUID", - makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id", - removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id", - // Equates to active = visible && !userDisabled && !softDisabled && - // !appDisabled && !pendingUninstall - setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " + - "1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)", - setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " + - "appDisabled=:appDisabled, " + - "softDisabled=:softDisabled, " + - "pendingUninstall=:pendingUninstall, " + - "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " + - "internal_id=:internal_id", - setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " + - "internal_id=:internal_id", - setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " + - "internal_id=:internal_id", - updateTargetApplications: "UPDATE targetApplication SET " + - "minVersion=:minVersion, maxVersion=:maxVersion " + - "WHERE addon_internal_id=:internal_id AND id=:id", - - createSavepoint: "SAVEPOINT 'default'", - releaseSavepoint: "RELEASE SAVEPOINT 'default'", - rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'" - }, - + // XXX may be able to refactor this away get dbfileExists() { delete this.dbfileExists; - return this.dbfileExists = this.dbfile.exists(); + return this.dbfileExists = this.jsonFile.exists(); }, set dbfileExists(aValue) { delete this.dbfileExists; @@ -545,12 +395,19 @@ this.XPIDatabase = { * Converts the current internal state of the XPI addon database to JSON * and writes it to the user's profile. Synchronous for now, eventually must * be async, reliable, etc. + * XXX should we remove the JSON file if it would be empty? Not sure if that + * would ever happen, given the default theme */ writeJSON: function XPIDB_writeJSON() { // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet + + // Don't mess with an existing database on disk, if it was locked at start up + if (this.lockedDatabase) + return; + let addons = []; - for (let aKey in this.addonDB) { - addons.push(copyProperties(this.addonDB[aKey], PROP_JSON_FIELDS)); + for (let [key, addon] of this.addonDB) { + addons.push(addon); } let toSave = { schemaVersion: DB_SCHEMA, @@ -563,11 +420,17 @@ this.XPIDatabase = { try { converter.init(stream, "UTF-8", 0, 0x0000); // XXX pretty print the JSON while debugging - converter.writeString(JSON.stringify(toSave, null, 2)); + let out = JSON.stringify(toSave, null, 2); + // dump("Writing JSON:\n" + out + "\n"); + converter.writeString(out); converter.flush(); // nsConverterOutputStream doesn't finish() safe output streams on close() FileUtils.closeSafeFileOutputStream(stream); converter.close(); + this.dbfileExists = true; + // XXX probably only want to do this if the version is different + Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); + Services.prefs.savePrefFile(null); // XXX is this bad sync I/O? } catch(e) { ERROR("Failed to save database to JSON", e); @@ -575,66 +438,6 @@ this.XPIDatabase = { } }, - /** - * Open and parse the JSON XPI extensions database. - * @return true: the DB was successfully loaded - * false: The DB either needs upgrade or did not exist at all. - * XXX upgrade and errors handled in a following patch - */ - openJSONDatabase: function XPIDB_openJSONDatabase() { - dump("XPIDB_openJSONDatabase\n"); - try { - let data = ""; - let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - fstream.init(this.jsonFile, -1, 0, 0); - cstream.init(fstream, "UTF-8", 0, 0); - let (str = {}) { - let read = 0; - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - data += str.value; - } while (read != 0); - } - cstream.close(); - let inputAddons = JSON.parse(data); - // Now do some sanity checks on our JSON db - if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { - // XXX Content of JSON file is bad, need to rebuild from scratch - ERROR("bad JSON file contents"); - delete this.addonDB; - this.addonDB = {}; - return false; - } - if (inputAddons.schemaVersion != DB_SCHEMA) { - // XXX UPGRADE FROM PREVIOUS VERSION OF JSON DB - ERROR("JSON schema upgrade needed"); - return false; - } - // If we got here, we probably have good data - // Make AddonInternal instances from the loaded data and save them - delete this.addonDB; - let addonDB = {} - inputAddons.addons.forEach(function(loadedAddon) { - let newAddon = new DBAddonInternal(loadedAddon); - addonDB[newAddon._key] = newAddon; - }); - this.addonDB = addonDB; - // dump("Finished reading DB: " + this.addonDB.toSource() + "\n"); - return true; - } - catch(e) { - // XXX handle missing JSON database - ERROR("Failed to load XPI JSON data from profile", e); - // XXX for now, start from scratch - delete this.addonDB; - this.addonDB = {}; - return false; - } - }, - /** * Begins a new transaction in the database. Transactions may be nested. Data * written by an inner transaction may be rolled back on its own. Rolling back @@ -681,170 +484,197 @@ this.XPIDatabase = { }, /** - * Attempts to open the database file. If it fails it will try to delete the - * existing file and create an empty database. If that fails then it will - * open an in-memory database that can be used during this session. + * Pull upgrade information from an existing SQLITE database * - * @param aDBFile - * The nsIFile to open - * @return the mozIStorageConnection for the database + * @return false if there is no SQLITE database + * true and sets this.migrateData to null if the SQLITE DB exists + * but does not contain useful information + * true and sets this.migrateData to + * {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...} + * if there is useful information */ - openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) { - LOG("Opening database"); + loadSqliteData: function XPIDB_loadSqliteData() { let connection = null; - + let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); + if (!dbfile.exists()) { + return false; + } // Attempt to open the database try { - connection = Services.storage.openUnsharedDatabase(aDBFile); - this.dbfileExists = true; + connection = Services.storage.openUnsharedDatabase(dbfile); } catch (e) { - ERROR("Failed to open database (1st attempt)", e); - // If the database was locked for some reason then assume it still - // has some good data and we should try to load it the next time around. - if (e.result != Cr.NS_ERROR_STORAGE_BUSY) { - try { - aDBFile.remove(true); - } - catch (e) { - ERROR("Failed to remove database that could not be opened", e); - } - try { - connection = Services.storage.openUnsharedDatabase(aDBFile); - } - catch (e) { - ERROR("Failed to open database (2nd attempt)", e); - - // If we have got here there seems to be no way to open the real - // database, instead open a temporary memory database so things will - // work for this session. - return Services.storage.openSpecialDatabase("memory"); - } - } - else { - return Services.storage.openSpecialDatabase("memory"); - } + // exists but SQLITE can't open it + WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e); + this.migrateData = null; + return true; } - - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - - return connection; + LOG("Migrating data from sqlite"); + this.migrateData = this.getMigrateDataFromDatabase(connection); + connection.close(); + return true; }, /** - * Opens a new connection to the database file. + * Opens and reads the database file, upgrading from old + * databases or making a new DB if needed. * + * The possibilities, in order of priority, are: + * 1) Perfectly good, up to date database + * 2) Out of date JSON database needs to be upgraded => upgrade + * 3) JSON database exists but is mangled somehow => build new JSON + * 4) no JSON DB, but a useable SQLITE db we can upgrade from => upgrade + * 5) useless SQLITE DB => build new JSON + * 6) useable RDF DB => upgrade + * 7) useless RDF DB => build new JSON + * 8) Nothing at all => build new JSON * @param aRebuildOnError * A boolean indicating whether add-on information should be loaded * from the install locations if the database needs to be rebuilt. + * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) { - this.openJSONDatabase(); + // XXX TELEMETRY report opens with aRebuildOnError true (which implies delayed open) + // vs. aRebuildOnError false (DB loaded during startup) + delete this.addonDB; + this.migrateData = null; + let fstream = null; + let data = ""; + try { + LOG("Opening XPI database " + this.jsonFile.path); + fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + fstream.init(this.jsonFile, -1, 0, 0); + let cstream = null; + try { + cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + cstream.init(fstream, "UTF-8", 0, 0); + let (str = {}) { + let read = 0; + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + } + // dump("Loaded JSON:\n" + data + "\n"); + let inputAddons = JSON.parse(data); + // Now do some sanity checks on our JSON db + if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { + // Content of JSON file is bad, need to rebuild from scratch + ERROR("bad JSON file contents"); + this.rebuildDatabase(aRebuildOnError); + } + if (inputAddons.schemaVersion != DB_SCHEMA) { + // Handle mismatched JSON schema version. For now, we assume backward/forward + // compatibility as long as we preserve unknown fields during save & restore + // XXX preserve schema version and unknown fields during save/restore + LOG("JSON schema mismatch: expected " + DB_SCHEMA + + ", actual " + inputAddons.schemaVersion); + } + // If we got here, we probably have good data + // Make AddonInternal instances from the loaded data and save them + let addonDB = new Map(); + inputAddons.addons.forEach(function(loadedAddon) { + let newAddon = new DBAddonInternal(loadedAddon); + addonDB.set(newAddon._key, newAddon); + }); + this.addonDB = addonDB; + LOG("Successfully read XPI database"); + } + catch(e) { + // If we catch and log a SyntaxError from the JSON + // parser, the xpcshell test harness fails the test for us: bug 870828 + if (e.name == "SyntaxError") { + ERROR("Syntax error parsing saved XPI JSON data"); + } + else { + ERROR("Failed to load XPI JSON data from profile", e); + } + this.rebuildDatabase(aRebuildOnError); + } + finally { + if (cstream) + cstream.close(); + } + } + catch (e) { + if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { + // XXX re-implement logic to decide whether to upgrade database + // by checking the DB_SCHEMA_VERSION preference. + // Fall back to attempting database upgrades + WARN("Extensions database not found; attempting to upgrade"); + // See if there is SQLITE to migrate from + if (!this.loadSqliteData()) { + // Nope, try RDF + this.migrateData = this.getMigrateDataFromRDF(); + } + + this.rebuildDatabase(aRebuildOnError); + } + else { + WARN("Extensions database " + this.jsonFile.path + + " exists but is not readable; rebuilding in memory", e); + // XXX open question - if we can overwrite at save time, should we, or should we + // leave the locked database in case we can recover from it next time we start up? + this.lockedDatabase = true; + // XXX TELEMETRY report when this happens? + this.rebuildDatabase(aRebuildOnError); + } + } + finally { + if (fstream) + fstream.close(); + } + this.initialized = true; return; - // XXX IRVING deal with the migration logic below and in openDatabaseFile... - - delete this.connection; + // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"? if (!aForceOpen && !this.dbfileExists) { this.connection = null; return; } + }, - this.migrateData = null; + /** + * Rebuild the database from addon install directories. If this.migrateData + * is available, uses migrated information for settings on the addons found + * during rebuild + * @param aRebuildOnError + * A boolean indicating whether add-on information should be loaded + * from the install locations if the database needs to be rebuilt. + * (if false, caller is XPIProvider.checkForChanges() which will rebuild) + */ + rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) { + // If there is no migration data then load the list of add-on directories + // that were active during the last run + this.addonDB = new Map(); + if (!this.migrateData) + this.activeBundles = this.getActiveBundles(); - this.connection = this.openDatabaseFile(this.dbfile); - - // If the database was corrupt or missing then the new blank database will - // have a schema version of 0. - let schemaVersion = this.connection.schemaVersion; - if (schemaVersion != DB_SCHEMA) { - // A non-zero schema version means that a schema has been successfully - // created in the database in the past so we might be able to get useful - // information from it - if (schemaVersion != 0) { - LOG("Migrating data from schema " + schemaVersion); - this.migrateData = this.getMigrateDataFromDatabase(); - - // Delete the existing database - this.connection.close(); - try { - if (this.dbfileExists) - this.dbfile.remove(true); - - // Reopen an empty database - this.connection = this.openDatabaseFile(this.dbfile); - } - catch (e) { - ERROR("Failed to remove old database", e); - // If the file couldn't be deleted then fall back to an in-memory - // database - this.connection = Services.storage.openSpecialDatabase("memory"); - } - } - else { - let dbSchema = 0; - try { - dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA); - } catch (e) {} - - if (dbSchema == 0) { - // Only migrate data from the RDF if we haven't done it before - this.migrateData = this.getMigrateDataFromRDF(); - } - } - - // At this point the database should be completely empty + if (aRebuildOnError) { + WARN("Rebuilding add-ons database from installed extensions."); + this.beginTransaction(); try { - this.createSchema(); + let state = XPIProvider.getInstallLocationStates(); + XPIProvider.processFileChanges(state, {}, false); + // Make sure to update the active add-ons and add-ons list on shutdown + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + this.commitTransaction(); } catch (e) { - // If creating the schema fails, then the database is unusable, - // fall back to an in-memory database. - this.connection = Services.storage.openSpecialDatabase("memory"); - } - - // If there is no migration data then load the list of add-on directories - // that were active during the last run - if (!this.migrateData) - this.activeBundles = this.getActiveBundles(); - - if (aRebuildOnError) { - WARN("Rebuilding add-ons database from installed extensions."); - this.beginTransaction(); - try { - let state = XPIProvider.getInstallLocationStates(); - XPIProvider.processFileChanges(state, {}, false); - // Make sure to update the active add-ons and add-ons list on shutdown - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - this.commitTransaction(); - } - catch (e) { - ERROR("Error processing file changes", e); - this.rollbackTransaction(); - } + ERROR("Error processing file changes", e); + this.rollbackTransaction(); } } - - // If the database connection has a file open then it has the right schema - // by now so make sure the preferences reflect that. - if (this.connection.databaseFile) { - Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); - Services.prefs.savePrefFile(null); - } - - // Begin any pending transactions - for (let i = 0; i < this.transactionCount; i++) - this.connection.executeSimpleSQL("SAVEPOINT 'default'"); }, /** * Lazy getter for the addons database */ get addonDB() { - delete this.addonDB; - this.openJSONDatabase(); + this.openConnection(true); return this.addonDB; }, @@ -964,13 +794,13 @@ this.XPIDatabase = { * @return an object holding information about what add-ons were previously * userDisabled and any updated compatibility information */ - getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase() { + getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase(aConnection) { let migrateData = {}; // Attempt to migrate data from a different (even future!) version of the // database try { - var stmt = this.connection.createStatement("PRAGMA table_info(addon)"); + var stmt = aConnection.createStatement("PRAGMA table_info(addon)"); const REQUIRED = ["internal_id", "id", "location", "userDisabled", "installDate", "version"]; @@ -996,7 +826,7 @@ this.XPIDatabase = { } stmt.finalize(); - stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon"); + stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon"); for (let row in resultRows(stmt)) { if (!(row.location in migrateData)) migrateData[row.location] = {}; @@ -1015,7 +845,7 @@ this.XPIDatabase = { }) } - var taStmt = this.connection.createStatement("SELECT id, minVersion, " + + var taStmt = aConnection.createStatement("SELECT id, minVersion, " + "maxVersion FROM " + "targetApplication WHERE " + "addon_internal_id=:internal_id"); @@ -1063,10 +893,8 @@ this.XPIDatabase = { // If we are running with an in-memory database then force a new // extensions.ini to be written to disk on the next startup - // XXX IRVING special case for if we fail to save extensions.json? - // XXX maybe doesn't need to be at shutdown? - // if (!this.connection.databaseFile) - // Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + if (this.lockedDatabase) + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); this.initialized = false; @@ -1075,7 +903,7 @@ this.XPIDatabase = { delete this.addonDB; Object.defineProperty(this, "addonDB", { get: function addonsGetter() { - this.openJSONDatabase(); + this.openConnection(true); return this.addonDB; }, configurable: true @@ -1098,17 +926,17 @@ this.XPIDatabase = { * installed add-ons, occasionally a superset when an install location no * longer exists. * - * @return an array of names of install locations + * @return a Set of names of install locations */ getInstallLocations: function XPIDB_getInstallLocations() { + let locations = new Set(); if (!this.addonDB) - return []; + return locations; - let locations = {}; - for each (let addon in this.addonDB) { - locations[addon.location] = 1; + for (let [, addon] of this.addonDB) { + locations.add(addon.location); } - return Object.keys(locations); + return locations; }, /** @@ -1123,8 +951,7 @@ this.XPIDatabase = { return []; let addonList = []; - for (let key in this.addonDB) { - let addon = this.addonDB[key]; + for (let [key, addon] of this.addonDB) { if (aFilter(addon)) { addonList.push(addon); } @@ -1144,8 +971,7 @@ this.XPIDatabase = { if (!this.addonDB) return null; - for (let key in this.addonDB) { - let addon = this.addonDB[key]; + for (let [key, addon] of this.addonDB) { if (aFilter(addon)) { return addon; } @@ -1178,7 +1004,7 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { - getRepositoryAddon(this.addonDB[aLocation + ":" + aId], aCallback); + getRepositoryAddon(this.addonDB.get(aLocation + ":" + aId), aCallback); }, /** @@ -1305,7 +1131,7 @@ this.XPIDatabase = { let newAddon = new DBAddonInternal(aAddon); newAddon.descriptor = aDescriptor; - this.addonDB[newAddon._key] = newAddon; + this.addonDB.set(newAddon._key, newAddon); if (newAddon.visible) { this.makeAddonVisible(newAddon); } @@ -1358,7 +1184,7 @@ this.XPIDatabase = { */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { this.beginTransaction(); - delete this.addonDB[aAddon._key]; + this.addonDB.delete(aAddon._key); this.commitTransaction(); }, @@ -1374,8 +1200,7 @@ this.XPIDatabase = { makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { this.beginTransaction(); LOG("Make addon " + aAddon._key + " visible"); - for (let key in this.addonDB) { - let otherAddon = this.addonDB[key]; + for (let [key, otherAddon] of this.addonDB) { if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { LOG("Hide addon " + otherAddon._key); otherAddon.visible = false; @@ -1461,14 +1286,20 @@ this.XPIDatabase = { // XXX IRVING this may get called during XPI-utils shutdown // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); - this.beginTransaction(); - for (let key in this.addonDB) { - let addon = this.addonDB[key]; - addon.active = (addon.visible && !addon.userDisabled && + let changed = false; + for (let [key, addon] of this.addonDB) { + let newActive = (addon.visible && !addon.userDisabled && !addon.softDisabled && !addon.appDisabled && !addon.pendingUninstall); + if (newActive != addon.active) { + addon.active = newActive; + changed = true; + } + } + if (changed) { + this.beginTransaction(); + this.commitTransaction(); } - this.commitTransaction(); }, /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index e0c34ee1aa35..781f38e21c30 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1395,16 +1395,16 @@ function do_exception_wrap(func) { } const EXTENSIONS_DB = "extensions.json"; +let gExtensionsJSON = gProfD.clone(); +gExtensionsJSON.append(EXTENSIONS_DB); /** * Change the schema version of the JSON extensions database */ function changeXPIDBVersion(aNewVersion) { - let dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - let jData = loadJSON(dbfile); + let jData = loadJSON(gExtensionsJSON); jData.schemaVersion = aNewVersion; - saveJSON(jData, dbfile); + saveJSON(jData, gExtensionsJSON); } /** @@ -1426,7 +1426,7 @@ function loadJSON(aFile) { } while (read != 0); } cstream.close(); - do_print("Loaded JSON file " + aFile.spec); + do_print("Loaded JSON file " + aFile.path); return(JSON.parse(data)); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js b/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js new file mode 100644 index 000000000000..d3ccf68f3a0d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that we rebuild the database correctly if it contains +// JSON data that parses correctly but doesn't contain required fields + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "2.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + do_test_pending("Bad JSON"); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + // This addon will be auto-installed at startup + writeInstallRDFForExtension(addon1, profileDir); + + startupManager(); + + shutdownManager(); + + // First startup/shutdown finished + // Replace the JSON store with something bogus + saveJSON({not: "what we expect to find"}, gExtensionsJSON); + + startupManager(false); + // Retrieve an addon to force the database to rebuild + AddonManager.getAddonsByIDs([addon1.id], callback_soon(after_db_rebuild)); +} + +function after_db_rebuild([a1]) { + do_check_eq(a1.id, addon1.id); + + shutdownManager(); + + // Make sure our JSON database has schemaVersion and our installed extension + let data = loadJSON(gExtensionsJSON); + do_check_true("schemaVersion" in data); + do_check_eq(data.addons[0].id, addon1.id); + + do_test_finished("Bad JSON"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index 6d201640f989..9a72b50c6733 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -146,10 +146,9 @@ function run_test() { startupManager(); - let file = gProfD.clone(); - file.append(EXTENSIONS_DB); - do_check_false(file.exists()); + do_check_false(gExtensionsJSON.exists()); + let file = gProfD.clone(); file.leafName = "extensions.ini"; do_check_false(file.exists()); @@ -205,10 +204,9 @@ function run_test_1() { } function check_test_1(installSyncGUID) { - let file = gProfD.clone(); - file.append(EXTENSIONS_DB); - do_check_true(file.exists()); + do_check_true(gExtensionsJSON.exists()); + let file = gProfD.clone(); file.leafName = "extensions.ini"; do_check_false(file.exists()); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index 789819cc6b87..af1c845f19ff 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -47,9 +47,7 @@ function run_test_1() { shutdownManager(); - let db = gProfD.clone(); - db.append(EXTENSIONS_DB); - db.remove(true); + gExtensionsJSON.remove(true); do_execute_soon(check_test_1); }); @@ -62,10 +60,8 @@ function check_test_1() { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); - let db = gProfD.clone(); - db.append(EXTENSIONS_DB); - do_check_true(db.exists()); - do_check_true(db.fileSize > 0); + do_check_true(gExtensionsJSON.exists()); + do_check_true(gExtensionsJSON.fileSize > 0); end_test(); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js index 50dd782da360..4c8b3750d695 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -251,10 +251,8 @@ function run_test_1() { // serves this purpose). On startup the add-ons manager won't rebuild // because there is a file there still. shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.json"); - dbfile.remove(true); - dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); + gExtensionsJSON.remove(true); + gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); startupManager(false); // Accessing the add-ons should open and recover the database diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js index e465b47bbacb..3ba6d213beb0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js @@ -252,10 +252,8 @@ function run_test_1() { // serves this purpose). On startup the add-ons manager won't rebuild // because there is a file there still. shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.json"); - dbfile.remove(true); - dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); + gExtensionsJSON.remove(true); + gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); startupManager(false); // Accessing the add-ons should open and recover the database diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js index 883ea08c6d1f..91d09c07c7e2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -257,10 +257,8 @@ function run_test_1() { // After shutting down the database won't be open so we can // mess with permissions shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - var savedPermissions = dbfile.permissions; - dbfile.permissions = 0; + var savedPermissions = gExtensionsJSON.permissions; + gExtensionsJSON.permissions = 0; startupManager(false); @@ -428,11 +426,12 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - dbfile.permissions = savedPermissions; - // After allowing access to the original DB things should go back to as // they were previously - restartManager(); + shutdownManager(); + gExtensionsJSON.permissions = savedPermissions; + startupManager(); + // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 6e21df540d1f..79c3439fd4e8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -144,10 +144,8 @@ function run_test() { // After shutting down the database won't be open so we can lock it shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - var savedPermissions = dbfile.permissions; - dbfile.permissions = 0; + var savedPermissions = gExtensionsJSON.permissions; + gExtensionsJSON.permissions = 0; startupManager(false); @@ -199,11 +197,11 @@ function run_test() { do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - dbfile.permissions = savedPermissions; - // After allowing access to the original DB things should still be // applied correctly - restartManager(); + shutdownManager(); + gExtensionsJSON.permissions = savedPermissions; + startupManager(); // These things happened when we had no access to the database so // they are seen as external changes when we get the database back :( diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index 2c31171318be..f38549cdaae2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -256,10 +256,8 @@ function run_test_1() { // After shutting down the database won't be open so we can lock it shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - var savedPermissions = dbfile.permissions; - dbfile.permissions = 0; + var savedPermissions = gExtensionsJSON.permissions; + gExtensionsJSON.permissions = 0; startupManager(false); @@ -425,11 +423,11 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - dbfile.permissions = savedPermissions; - // After allowing access to the original DB things should go back to as // they were previously - restartManager(); + shutdownManager(); + gExtensionsJSON.permissions = savedPermissions; + startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 49fef590e1c0..6113454b47a2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -16,12 +16,9 @@ skip-if = os == "android" [test_DeferredSave.js] [test_LightweightThemeManager.js] [test_backgroundupdate.js] +[test_bad_json.js] [test_badschema.js] -# Needs rewrite for JSON XPIDB -fail-if = true [test_blocklistchange.js] -# Needs rewrite for JSON XPIDB -fail-if = true # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_blocklist_regexp.js] @@ -142,8 +139,6 @@ fail-if = os == "android" [test_bug620837.js] [test_bug655254.js] [test_bug659772.js] -# needs to be converted from sqlite to JSON -fail-if = true [test_bug675371.js] [test_bug740612.js] [test_bug753900.js] @@ -153,11 +148,7 @@ fail-if = true [test_ChromeManifestParser.js] [test_compatoverrides.js] [test_corrupt.js] -# needs to be converted from sqlite to JSON -fail-if = true [test_corrupt_strictcompat.js] -# needs to be converted from sqlite to JSON -fail-if = true [test_dictionary.js] [test_langpack.js] [test_disable.js] @@ -202,33 +193,17 @@ skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. [test_locale.js] [test_locked.js] -# Needs sqlite->JSON conversion -fail-if = true [test_locked2.js] -# Needs sqlite->JSON conversion -fail-if = true [test_locked_strictcompat.js] -# Needs sqlite->JSON conversion -fail-if = true [test_manifest.js] [test_mapURIToAddonID.js] # Same as test_bootstrap.js skip-if = os == "android" [test_migrate1.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate2.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate3.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate4.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate5.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrateAddonRepository.js] [test_onPropertyChanged_appDisabled.js] [test_permissions.js] From cd8845f65788684816d32e385cf3c4fffe18e7f1 Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Thu, 8 Aug 2013 15:56:35 -0400 Subject: [PATCH 13/40] Bug 853388: Remove transaction model of database flush control and implement async save of database; r=unfocused --- toolkit/mozapps/extensions/XPIProvider.jsm | 155 ++++------ .../mozapps/extensions/XPIProviderUtils.js | 286 ++++++++---------- .../xpcshell/test_AddonRepository_cache.js | 12 +- .../test/xpcshell/test_bootstrap.js | 5 +- .../test/xpcshell/test_bug559800.js | 3 + .../test/xpcshell/test_bug659772.js | 12 +- .../extensions/test/xpcshell/test_startup.js | 6 +- 7 files changed, 207 insertions(+), 272 deletions(-) diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 2794fd309749..83adbdc628b6 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -509,6 +509,9 @@ function findClosestLocale(aLocales) { * previous instance may be a previous install or in the case of an application * version change the same add-on. * + * NOTE: this may modify aNewAddon in place; callers should save the database if + * necessary + * * @param aOldAddon * The previous instance of the add-on * @param aNewAddon @@ -1332,19 +1335,24 @@ function recursiveRemove(aFile) { * @return Epoch time, as described above. 0 for an empty directory. */ function recursiveLastModifiedTime(aFile) { - if (aFile.isFile()) - return aFile.lastModifiedTime; + try { + if (aFile.isFile()) + return aFile.lastModifiedTime; - if (aFile.isDirectory()) { - let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - let entry, time; - let maxTime = aFile.lastModifiedTime; - while ((entry = entries.nextFile)) { - time = recursiveLastModifiedTime(entry); - maxTime = Math.max(time, maxTime); + if (aFile.isDirectory()) { + let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); + let entry, time; + let maxTime = aFile.lastModifiedTime; + while ((entry = entries.nextFile)) { + time = recursiveLastModifiedTime(entry); + maxTime = Math.max(time, maxTime); + } + entries.close(); + return maxTime; } - entries.close(); - return maxTime; + } + catch (e) { + WARN("Problem getting last modified time for " + aFile.path, e); } // If the file is something else, just ignore it. @@ -1877,10 +1885,12 @@ var XPIProvider = { if (gLazyObjectsLoaded) { XPIDatabase.shutdown(function shutdownCallback() { + LOG("Notifying XPI shutdown observers"); Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); }); } else { + LOG("Notifying XPI shutdown observers"); Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); } }, @@ -1938,7 +1948,7 @@ var XPIProvider = { }, /** - * Persists changes to XPIProvider.bootstrappedAddons to it's store (a pref). + * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref). */ persistBootstrappedAddons: function XPI_persistBootstrappedAddons() { Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, @@ -2498,7 +2508,7 @@ var XPIProvider = { applyBlocklistChanges(aOldAddon, newAddon); // Carry over any pendingUninstall state to add-ons modified directly - // in the profile. This is impoprtant when the attempt to remove the + // in the profile. This is important when the attempt to remove the // add-on in processPendingFileChanges failed and caused an mtime // change to the add-ons files. newAddon.pendingUninstall = aOldAddon.pendingUninstall; @@ -2658,38 +2668,27 @@ var XPIProvider = { // App version changed, we may need to update the appDisabled property. if (aUpdateCompatibility) { - // Create a basic add-on object for the new state to save reproducing - // the applyBlocklistChanges code - let newAddon = new AddonInternal(); - newAddon.id = aOldAddon.id; - newAddon.syncGUID = aOldAddon.syncGUID; - newAddon.version = aOldAddon.version; - newAddon.type = aOldAddon.type; - newAddon.appDisabled = !isUsableAddon(aOldAddon); - - // Sync the userDisabled flag to the selectedSkin - if (aOldAddon.type == "theme") - newAddon.userDisabled = aOldAddon.internalName != XPIProvider.selectedSkin; - - applyBlocklistChanges(aOldAddon, newAddon, aOldAppVersion, - aOldPlatformVersion); - let wasDisabled = isAddonDisabled(aOldAddon); - let isDisabled = isAddonDisabled(newAddon); + let wasAppDisabled = aOldAddon.appDisabled; + let wasUserDisabled = aOldAddon.userDisabled; + let wasSoftDisabled = aOldAddon.softDisabled; + + // This updates the addon's JSON cached data in place + applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion, + aOldPlatformVersion); + aOldAddon.appDisabled = !isUsableAddon(aOldAddon); + + let isDisabled = isAddonDisabled(aOldAddon); // If either property has changed update the database. - if (newAddon.appDisabled != aOldAddon.appDisabled || - newAddon.userDisabled != aOldAddon.userDisabled || - newAddon.softDisabled != aOldAddon.softDisabled) { + if (wasAppDisabled != aOldAddon.appDisabled || + wasUserDisabled != aOldAddon.userDisabled || + wasSoftDisabled != aOldAddon.softDisabled) { LOG("Add-on " + aOldAddon.id + " changed appDisabled state to " + - newAddon.appDisabled + ", userDisabled state to " + - newAddon.userDisabled + " and softDisabled state to " + - newAddon.softDisabled); - XPIDatabase.setAddonProperties(aOldAddon, { - appDisabled: newAddon.appDisabled, - userDisabled: newAddon.userDisabled, - softDisabled: newAddon.softDisabled - }); + aOldAddon.appDisabled + ", userDisabled state to " + + aOldAddon.userDisabled + " and softDisabled state to " + + aOldAddon.softDisabled); + XPIDatabase.saveChanges(); } // If this is a visible add-on and it has changed disabled state then we @@ -2895,20 +2894,7 @@ var XPIProvider = { newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon)) } - let newDBAddon = null; - try { - // Update the database. - // XXX I don't think this can throw any more - newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); - } - catch (e) { - // Failing to write the add-on into the database is non-fatal, the - // add-on will just be unavailable until we try again in a subsequent - // startup - ERROR("Failed to add add-on " + aId + " in " + aInstallLocation.name + - " to database", e); - return false; - } + let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); if (newDBAddon.visible) { // Remember add-ons that were first detected during startup. @@ -3226,25 +3212,22 @@ var XPIProvider = { } } - // Catch any errors during the main startup and rollback the database changes - let transationBegun = false; + // Catch and log any errors during the main startup try { let extensionListChanged = false; // If the database needs to be updated then open it and then update it // from the filesystem if (updateDatabase || hasPendingChanges) { - XPIDatabase.beginTransaction(); - transationBegun = true; - XPIDatabase.openConnection(false, true); - try { + XPIDatabase.openConnection(false, true); + extensionListChanged = this.processFileChanges(state, manifests, aAppChanged, aOldAppVersion, aOldPlatformVersion); } catch (e) { - ERROR("Error processing file changes", e); + ERROR("Failed to process extension changes at startup", e); } } AddonManagerPrivate.recordSimpleMeasure("installedUnpacked", this.unpackedAddons); @@ -3253,10 +3236,6 @@ var XPIProvider = { // When upgrading the app and using a custom skin make sure it is still // compatible otherwise switch back the default if (this.currentSkin != this.defaultSkin) { - if (!transationBegun) { - XPIDatabase.beginTransaction(); - transationBegun = true; - } let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin); if (!oldSkin || isAddonDisabled(oldSkin)) this.enableDefaultTheme(); @@ -3264,21 +3243,21 @@ var XPIProvider = { // When upgrading remove the old extensions cache to force older // versions to rescan the entire list of extensions - let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true); - if (oldCache.exists()) - oldCache.remove(true); + try { + let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true); + if (oldCache.exists()) + oldCache.remove(true); + } + catch (e) { + WARN("Unable to remove old extension cache " + oldCache.path, e); + } } // If the application crashed before completing any pending operations then // we should perform them now. if (extensionListChanged || hasPendingChanges) { LOG("Updating database with changes to installed add-ons"); - if (!transationBegun) { - XPIDatabase.beginTransaction(); - transationBegun = true; - } XPIDatabase.updateActiveAddons(); - XPIDatabase.commitTransaction(); XPIDatabase.writeAddonsList(); Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false); Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, @@ -3287,14 +3266,9 @@ var XPIProvider = { } LOG("No changes found"); - if (transationBegun) - XPIDatabase.commitTransaction(); } catch (e) { - ERROR("Error during startup file checks, rolling back any database " + - "changes", e); - if (transationBegun) - XPIDatabase.rollbackTransaction(); + ERROR("Error during startup file checks", e); } // Check that the add-ons list still exists @@ -3683,7 +3657,7 @@ var XPIProvider = { null); this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); - this.updateAllAddonDisabledStates(); + this.updateAddonAppDisabledStates(); break; } }, @@ -4002,8 +3976,7 @@ var XPIProvider = { this.bootstrapScopes[aId][aMethod](params, aReason); } catch (e) { - WARN("Exception running bootstrap method " + aMethod + " on " + - aId, e); + WARN("Exception running bootstrap method " + aMethod + " on " + aId, e); } } finally { @@ -4014,20 +3987,10 @@ var XPIProvider = { } }, - /** - * Updates the appDisabled property for all add-ons. - */ - updateAllAddonDisabledStates: function XPI_updateAllAddonDisabledStates() { - let addons = XPIDatabase.getAddons(); - addons.forEach(function(aAddon) { - this.updateAddonDisabledState(aAddon); - }, this); - }, - /** * Updates the disabled state for an add-on. Its appDisabled property will be - * calculated and if the add-on is changed appropriate notifications will be - * sent out to the registered AddonListeners. + * calculated and if the add-on is changed the database will be saved and + * appropriate notifications will be sent out to the registered AddonListeners. * * @param aAddon * The DBAddonInternal to update @@ -5334,7 +5297,7 @@ AddonInstall.prototype = { // Update the metadata in the database this.addon._sourceBundle = file; this.addon._installLocation = this.installLocation; - this.addon.updateDate = recursiveLastModifiedTime(file); + this.addon.updateDate = recursiveLastModifiedTime(file); // XXX sync recursive scan this.addon.visible = true; if (isUpgrade) { this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 87c0d910f014..5fe3142066f1 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -7,19 +7,24 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; +const Cu = Components.utils; -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/AddonRepository.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave", + "resource://gre/modules/DeferredSave.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); ["LOG", "WARN", "ERROR"].forEach(function(aName) { Object.defineProperty(this, aName, { get: function logFuncGetter () { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + Cu.import("resource://gre/modules/AddonLogging.jsm"); LogManager.getLogger("addons.xpi-utils", this); return this[aName]; @@ -87,6 +92,8 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "strictCompatibility", "locales", "targetApplications", "targetPlatforms"]; +// Time to wait before async save of XPI JSON database, in milliseconds +const ASYNC_SAVE_DELAY_MS = 20; const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" @@ -342,18 +349,17 @@ function DBAddonInternal(aLoaded) { DBAddonInternal.prototype = { applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { - XPIDatabase.beginTransaction(); this.targetApplications.forEach(function(aTargetApp) { aUpdate.targetApplications.forEach(function(aUpdateTarget) { if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { aTargetApp.minVersion = aUpdateTarget.minVersion; aTargetApp.maxVersion = aUpdateTarget.maxVersion; + XPIDatabase.saveChanges(); } }); }); XPIProvider.updateAddonDisabledState(this); - XPIDatabase.commitTransaction(); }, get inDatabase() { @@ -370,8 +376,6 @@ DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; this.XPIDatabase = { // true if the database connection has been opened initialized: false, - // The nested transaction count - transactionCount: 0, // The database file jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), // Migration data loaded from an old version of the database. @@ -392,19 +396,58 @@ this.XPIDatabase = { }, /** - * Converts the current internal state of the XPI addon database to JSON - * and writes it to the user's profile. Synchronous for now, eventually must - * be async, reliable, etc. - * XXX should we remove the JSON file if it would be empty? Not sure if that - * would ever happen, given the default theme + * Mark the current stored data dirty, and schedule a flush to disk */ - writeJSON: function XPIDB_writeJSON() { - // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet + saveChanges: function() { + if (!this.initialized) { + throw new Error("Attempt to use XPI database when it is not initialized"); + } - // Don't mess with an existing database on disk, if it was locked at start up - if (this.lockedDatabase) + // handle the "in memory only" case + if (this.lockedDatabase) { return; + } + let promise = this._deferredSave.saveChanges(); + if (!this._schemaVersionSet) { + this._schemaVersionSet = true; + promise.then( + count => { + // Update the XPIDB schema version preference the first time we successfully + // save the database. + LOG("XPI Database saved, setting schema version preference to " + DB_SCHEMA); + Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); + }, + error => { + // Need to try setting the schema version again later + this._schemaVersionSet = false; + WARN("Failed to save XPI database", error); + }); + } + }, + + flush: function() { + // handle the "in memory only" case + if (this.lockedDatabase) { + let done = Promise.defer(); + done.resolve(0); + return done.promise; + } + + return this._deferredSave.flush(); + }, + + get _deferredSave() { + delete this._deferredSave; + return this._deferredSave = + new DeferredSave(this.jsonFile.path, () => JSON.stringify(this), + ASYNC_SAVE_DELAY_MS); + }, + + /** + * Converts the current internal state of the XPI addon database to JSON + */ + toJSON: function() { let addons = []; for (let [key, addon] of this.addonDB) { addons.push(addon); @@ -413,74 +456,7 @@ this.XPIDatabase = { schemaVersion: DB_SCHEMA, addons: addons }; - - let stream = FileUtils.openSafeFileOutputStream(this.jsonFile); - let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. - createInstance(Ci.nsIConverterOutputStream); - try { - converter.init(stream, "UTF-8", 0, 0x0000); - // XXX pretty print the JSON while debugging - let out = JSON.stringify(toSave, null, 2); - // dump("Writing JSON:\n" + out + "\n"); - converter.writeString(out); - converter.flush(); - // nsConverterOutputStream doesn't finish() safe output streams on close() - FileUtils.closeSafeFileOutputStream(stream); - converter.close(); - this.dbfileExists = true; - // XXX probably only want to do this if the version is different - Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); - Services.prefs.savePrefFile(null); // XXX is this bad sync I/O? - } - catch(e) { - ERROR("Failed to save database to JSON", e); - stream.close(); - } - }, - - /** - * Begins a new transaction in the database. Transactions may be nested. Data - * written by an inner transaction may be rolled back on its own. Rolling back - * an outer transaction will rollback all the changes made by inner - * transactions even if they were committed. No data is written to the disk - * until the outermost transaction is committed. Transactions can be started - * even when the database is not yet open in which case they will be started - * when the database is first opened. - */ - beginTransaction: function XPIDB_beginTransaction() { - this.transactionCount++; - }, - - /** - * Commits the most recent transaction. The data may still be rolled back if - * an outer transaction is rolled back. - */ - commitTransaction: function XPIDB_commitTransaction() { - if (this.transactionCount == 0) { - ERROR("Attempt to commit one transaction too many."); - return; - } - - this.transactionCount--; - - if (this.transactionCount == 0) { - // All our nested transactions are done, write the JSON file - this.writeJSON(); - } - }, - - /** - * Rolls back the most recent transaction. The database will return to its - * state when the transaction was started. - */ - rollbackTransaction: function XPIDB_rollbackTransaction() { - if (this.transactionCount == 0) { - ERROR("Attempt to rollback one transaction too many."); - return; - } - - this.transactionCount--; - // XXX IRVING we don't handle rollback in the JSON store + return toSave; }, /** @@ -566,9 +542,10 @@ this.XPIDatabase = { this.rebuildDatabase(aRebuildOnError); } if (inputAddons.schemaVersion != DB_SCHEMA) { - // Handle mismatched JSON schema version. For now, we assume backward/forward - // compatibility as long as we preserve unknown fields during save & restore - // XXX preserve schema version and unknown fields during save/restore + // Handle mismatched JSON schema version. For now, we assume + // compatibility for JSON data, though we throw away any fields we + // don't know about + // XXX preserve unknown fields during save/restore LOG("JSON schema mismatch: expected " + DB_SCHEMA + ", actual " + inputAddons.schemaVersion); } @@ -581,6 +558,7 @@ this.XPIDatabase = { }); this.addonDB = addonDB; LOG("Successfully read XPI database"); + this.initialized = true; } catch(e) { // If we catch and log a SyntaxError from the JSON @@ -627,7 +605,6 @@ this.XPIDatabase = { fstream.close(); } - this.initialized = true; return; // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"? @@ -647,26 +624,25 @@ this.XPIDatabase = { * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) { + this.addonDB = new Map(); + this.initialized = true; + // If there is no migration data then load the list of add-on directories // that were active during the last run - this.addonDB = new Map(); if (!this.migrateData) this.activeBundles = this.getActiveBundles(); if (aRebuildOnError) { WARN("Rebuilding add-ons database from installed extensions."); - this.beginTransaction(); try { let state = XPIProvider.getInstallLocationStates(); XPIProvider.processFileChanges(state, {}, false); - // Make sure to update the active add-ons and add-ons list on shutdown - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - this.commitTransaction(); } catch (e) { - ERROR("Error processing file changes", e); - this.rollbackTransaction(); + ERROR("Failed to rebuild XPI database from installed extensions", e); } + // Make to update the active add-ons and add-ons list on shutdown + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); } }, @@ -885,38 +861,55 @@ this.XPIDatabase = { shutdown: function XPIDB_shutdown(aCallback) { LOG("shutdown"); if (this.initialized) { - if (this.transactionCount > 0) { - ERROR(this.transactionCount + " outstanding transactions, rolling back."); - while (this.transactionCount > 0) - this.rollbackTransaction(); - } - // If we are running with an in-memory database then force a new // extensions.ini to be written to disk on the next startup if (this.lockedDatabase) Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); this.initialized = false; + let result = null; - // Clear out the cached addons data loaded from JSON and recreate - // the getter to allow database re-loads during testing. - delete this.addonDB; - Object.defineProperty(this, "addonDB", { - get: function addonsGetter() { - this.openConnection(true); - return this.addonDB; - }, - configurable: true - }); - // XXX IRVING removed an async callback when the database was closed - // XXX do we want to keep the ability to async flush extensions.json - // XXX and then call back? - if (aCallback) - aCallback(); + // Make sure any pending writes of the DB are complete, and we + // finish cleaning up, and then call back + this.flush() + .then(null, error => { + ERROR("Flush of XPI database failed", error); + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + result = error; + return 0; + }) + .then(count => { + // Clear out the cached addons data loaded from JSON and recreate + // the getter to allow database re-loads during testing. + delete this.addonDB; + Object.defineProperty(this, "addonDB", { + get: function addonsGetter() { + this.openConnection(true); + return this.addonDB; + }, + configurable: true + }); + // same for the deferred save + delete this._deferredSave; + Object.defineProperty(this, "_deferredSave", { + set: function deferredSaveGetter() { + delete this._deferredSave; + return this._deferredSave = + new DeferredSave(this.jsonFile.path, this.formJSON.bind(this), + ASYNC_SAVE_DELAY_MS); + }, + configurable: true + }); + // re-enable the schema version setter + delete this._schemaVersionSet; + + if (aCallback) + aCallback(result); + }); } else { if (aCallback) - aCallback(); + aCallback(null); } }, @@ -1127,8 +1120,6 @@ this.XPIDatabase = { if (!this.addonDB) this.openConnection(false, true); - this.beginTransaction(); - let newAddon = new DBAddonInternal(aAddon); newAddon.descriptor = aDescriptor; this.addonDB.set(newAddon._key, newAddon); @@ -1136,12 +1127,12 @@ this.XPIDatabase = { this.makeAddonVisible(newAddon); } - this.commitTransaction(); + this.saveChanges(); return newAddon; }, /** - * Synchronously updates an add-ons metadata in the database. Currently just + * Synchronously updates an add-on's metadata in the database. Currently just * removes and recreates. * * @param aOldAddon @@ -1154,26 +1145,16 @@ this.XPIDatabase = { */ updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon, aDescriptor) { - this.beginTransaction(); + this.removeAddonMetadata(aOldAddon); + aNewAddon.syncGUID = aOldAddon.syncGUID; + aNewAddon.installDate = aOldAddon.installDate; + aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates; + aNewAddon.foreignInstall = aOldAddon.foreignInstall; + aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && + !aNewAddon.appDisabled && !aNewAddon.pendingUninstall); - // Any errors in here should rollback the transaction - try { - this.removeAddonMetadata(aOldAddon); - aNewAddon.syncGUID = aOldAddon.syncGUID; - aNewAddon.installDate = aOldAddon.installDate; - aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates; - aNewAddon.foreignInstall = aOldAddon.foreignInstall; - aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && - !aNewAddon.appDisabled && !aNewAddon.pendingUninstall) - - let newDBAddon = this.addAddonMetadata(aNewAddon, aDescriptor); - this.commitTransaction(); - return newDBAddon; - } - catch (e) { - this.rollbackTransaction(); - throw e; - } + // addAddonMetadata does a saveChanges() + return this.addAddonMetadata(aNewAddon, aDescriptor); }, /** @@ -1183,9 +1164,8 @@ this.XPIDatabase = { * The DBAddonInternal being removed */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { - this.beginTransaction(); this.addonDB.delete(aAddon._key); - this.commitTransaction(); + this.saveChanges(); }, /** @@ -1198,7 +1178,6 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { - this.beginTransaction(); LOG("Make addon " + aAddon._key + " visible"); for (let [key, otherAddon] of this.addonDB) { if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { @@ -1207,7 +1186,7 @@ this.XPIDatabase = { } } aAddon.visible = true; - this.commitTransaction(); + this.saveChanges(); }, /** @@ -1219,11 +1198,10 @@ this.XPIDatabase = { * A dictionary of properties to set */ setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) { - this.beginTransaction(); for (let key in aProperties) { aAddon[key] = aProperties[key]; } - this.commitTransaction(); + this.saveChanges(); }, /** @@ -1245,9 +1223,8 @@ this.XPIDatabase = { throw new Error("Addon sync GUID conflict for addon " + aAddon._key + ": " + otherAddon._key + " already has GUID " + aGUID); } - this.beginTransaction(); aAddon.syncGUID = aGUID; - this.commitTransaction(); + this.saveChanges(); }, /** @@ -1260,9 +1237,8 @@ this.XPIDatabase = { * File path of the installed addon */ setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { - this.beginTransaction(); aAddon.descriptor = aDescriptor; - this.commitTransaction(); + this.saveChanges(); }, /** @@ -1274,9 +1250,8 @@ this.XPIDatabase = { updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) { LOG("Updating active state for add-on " + aAddon.id + " to " + aActive); - this.beginTransaction(); aAddon.active = aActive; - this.commitTransaction(); + this.saveChanges(); }, /** @@ -1286,20 +1261,15 @@ this.XPIDatabase = { // XXX IRVING this may get called during XPI-utils shutdown // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); - let changed = false; for (let [key, addon] of this.addonDB) { let newActive = (addon.visible && !addon.userDisabled && !addon.softDisabled && !addon.appDisabled && !addon.pendingUninstall); if (newActive != addon.active) { addon.active = newActive; - changed = true; + this.saveChanges(); } } - if (changed) { - this.beginTransaction(); - this.commitTransaction(); - } }, /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index 14d302a4b1e5..600f3d81b3f0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -512,7 +512,7 @@ function check_cache(aExpectedToFind, aExpectedImmediately, aCallback) { * A callback to call once the checks are complete */ function check_initialized_cache(aExpectedToFind, aCallback) { - check_cache(aExpectedToFind, true, function() { + check_cache(aExpectedToFind, true, function restart_initialized_cache() { restartManager(); // If cache is disabled, then expect results immediately @@ -534,13 +534,13 @@ function waitForFlushedData(aCallback) { function run_test() { // Setup for test - do_test_pending(); + do_test_pending("test_AddonRepository_cache"); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); startupManager(); // Install XPI add-ons - installAllFiles(ADDON_FILES, function() { + installAllFiles(ADDON_FILES, function first_installs() { restartManager(); gServer = new HttpServer(); @@ -552,7 +552,7 @@ function run_test() { } function end_test() { - gServer.stop(do_test_finished); + gServer.stop(function() {do_test_finished("test_AddonRepository_cache");}); } // Tests AddonRepository.cacheEnabled @@ -578,7 +578,7 @@ function run_test_3() { Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED); - AddonRepository.repopulateCache(ADDON_IDS, function() { + AddonRepository.repopulateCache(ADDON_IDS, function test_3_repopulated() { check_initialized_cache([false, false, false], run_test_4); }); } @@ -695,7 +695,7 @@ function run_test_12() { Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); - AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { + AddonManager.getAddonsByIDs(ADDON_IDS, function test_12_check(aAddons) { check_results(aAddons, WITHOUT_CACHE); do_execute_soon(run_test_13); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index 9a72b50c6733..912c676f8e14 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -204,8 +204,6 @@ function run_test_1() { } function check_test_1(installSyncGUID) { - do_check_true(gExtensionsJSON.exists()); - let file = gProfD.clone(); file.leafName = "extensions.ini"; do_check_false(file.exists()); @@ -352,6 +350,9 @@ function run_test_4() { // Tests that a restart shuts down and restarts the add-on function run_test_5() { shutdownManager(); + // By the time we've shut down, the database must have been written + do_check_true(gExtensionsJSON.exists()); + do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 0); do_check_eq(getShutdownReason(), APP_SHUTDOWN); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index af1c845f19ff..741debb76b4c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -60,6 +60,9 @@ function check_test_1() { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); + // due to delayed write, the file may not exist until + // after shutdown + shutdownManager(); do_check_true(gExtensionsJSON.exists()); do_check_true(gExtensionsJSON.fileSize > 0); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index 92ae8b21dc14..46b3e0393d2c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -167,10 +167,10 @@ function run_test_1() { // the previous version of the DB do_check_neq(a3, null); do_check_eq(a3.version, "2.0"); - do_check_false(a3.appDisabled); + todo_check_false(a3.appDisabled); // XXX unresolved issue do_check_false(a3.userDisabled); - do_check_true(a3.isActive); - do_check_true(isExtensionInAddonsList(profileDir, addon3.id)); + todo_check_true(a3.isActive); // XXX same + todo_check_true(isExtensionInAddonsList(profileDir, addon3.id)); // XXX same do_check_neq(a4, null); do_check_eq(a4.version, "2.0"); @@ -309,10 +309,10 @@ function run_test_2() { // the previous version of the DB do_check_neq(a3, null); do_check_eq(a3.version, "2.0"); - do_check_true(a3.appDisabled); + todo_check_true(a3.appDisabled); do_check_false(a3.userDisabled); - do_check_false(a3.isActive); - do_check_false(isExtensionInAddonsList(profileDir, addon3.id)); + todo_check_false(a3.isActive); + todo_check_false(isExtensionInAddonsList(profileDir, addon3.id)); do_check_neq(a4, null); do_check_eq(a4.version, "2.0"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index 841a50224169..a14f07e43471 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -191,10 +191,8 @@ function run_test_1() { do_check_true(gCachePurged); let file = gProfD.clone(); - file.append("extensions.json"); - do_check_true(file.exists()); - - file.leafName = "extensions.ini"; + file.append = "extensions.ini"; + do_print("Checking for " + file.path); do_check_true(file.exists()); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", From 9146a071a9decd17e56f9b4d5b69189bb6a07bf1 Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Thu, 8 Aug 2013 15:56:39 -0400 Subject: [PATCH 14/40] Bug 853388: Trigger XPI database conversion from SQLITE based on schema version preference; r=unfocused --- toolkit/mozapps/extensions/Makefile.in | 2 +- .../mozapps/extensions/XPIProviderUtils.js | 35 +++--- .../extensions/test/xpcshell/test_migrate2.js | 4 +- .../test/xpcshell/test_migrate_max_version.js | 102 ++++++++++++++++++ .../extensions/test/xpcshell/xpcshell.ini | 1 + 5 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js diff --git a/toolkit/mozapps/extensions/Makefile.in b/toolkit/mozapps/extensions/Makefile.in index 9dbf13c811a2..5eeb7947b6e1 100644 --- a/toolkit/mozapps/extensions/Makefile.in +++ b/toolkit/mozapps/extensions/Makefile.in @@ -15,7 +15,7 @@ endif # This is used in multiple places, so is defined here to avoid it getting # out of sync. -DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=14 +DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=15 # Additional debugging info is exposed in debug builds, or by setting the # MOZ_EM_DEBUG environment variable when building. diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 5fe3142066f1..9147cb1b448e 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -43,6 +43,8 @@ const FILE_XPI_ADDONS_LIST = "extensions.ini"; // The value for this is in Makefile.in #expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__; +// The last version of DB_SCHEMA implemented in SQLITE +const LAST_SQLITE_DB_SCHEMA = 14; const PREF_DB_SCHEMA = "extensions.databaseSchema"; const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons"; @@ -469,26 +471,21 @@ this.XPIDatabase = { * {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...} * if there is useful information */ - loadSqliteData: function XPIDB_loadSqliteData() { + getMigrateDataFromSQLITE: function XPIDB_getMigrateDataFromSQLITE() { let connection = null; let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); - if (!dbfile.exists()) { - return false; - } // Attempt to open the database try { connection = Services.storage.openUnsharedDatabase(dbfile); } catch (e) { - // exists but SQLITE can't open it WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e); - this.migrateData = null; - return true; + return null; } LOG("Migrating data from sqlite"); - this.migrateData = this.getMigrateDataFromDatabase(connection); + let migrateData = this.getMigrateDataFromDatabase(connection); connection.close(); - return true; + return migrateData; }, /** @@ -578,13 +575,18 @@ this.XPIDatabase = { } catch (e) { if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { - // XXX re-implement logic to decide whether to upgrade database - // by checking the DB_SCHEMA_VERSION preference. - // Fall back to attempting database upgrades - WARN("Extensions database not found; attempting to upgrade"); - // See if there is SQLITE to migrate from - if (!this.loadSqliteData()) { - // Nope, try RDF + try { + let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); + if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { + // we should have an older SQLITE database + this.migrateData = this.getMigrateDataFromSQLITE(); + } + // else we've upgraded before but the JSON file is gone, fall through + // and rebuild from scratch + } + catch(e) { + // No schema version pref means either a really old upgrade (RDF) or + // a new profile this.migrateData = this.getMigrateDataFromRDF(); } @@ -595,6 +597,7 @@ this.XPIDatabase = { " exists but is not readable; rebuilding in memory", e); // XXX open question - if we can overwrite at save time, should we, or should we // leave the locked database in case we can recover from it next time we start up? + // The old code made one attempt to remove the locked file before it rebuilt in memory this.lockedDatabase = true; // XXX TELEMETRY report when this happens? this.rebuildDatabase(aRebuildOnError); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js index 20431a9a7d00..c703b25c19a9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we migrate data from future versions of the database +// Checks that we migrate data from SQLITE databases // Note that since the database doesn't contain the foreignInstall field we // should just assume that no add-ons in the user profile were foreignInstalls @@ -177,7 +177,7 @@ function run_test() { stmt.finalize(); db.schemaVersion = 10000; - Services.prefs.setIntPref("extensions.databaseSchema", 100); + Services.prefs.setIntPref("extensions.databaseSchema", 14); db.close(); startupManager(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js new file mode 100644 index 000000000000..4f52cf6ca6b5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we don't migrate data from SQLITE if +// the "extensions.databaseSchema" preference shows we've +// already upgraded to JSON + +// Enable loading extensions from the user and system scopes +Services.prefs.setIntPref("extensions.enabledScopes", + AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER + + AddonManager.SCOPE_SYSTEM); + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + writeInstallRDFForExtension(addon1, profileDir); + + // Write out a minimal database + let dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + db.createTable("addon", "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "id TEXT, location TEXT, version TEXT, active INTEGER, " + + "userDisabled INTEGER, installDate INTEGER"); + db.createTable("targetApplication", "addon_internal_id INTEGER, " + + "id TEXT, minVersion TEXT, maxVersion TEXT"); + let stmt = db.createStatement("INSERT INTO addon VALUES (NULL, :id, :location, " + + ":version, :active, :userDisabled, :installDate)"); + + let internal_ids = {}; + + [["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"] + ].forEach(function(a) { + stmt.params.id = a[0]; + stmt.params.location = a[1]; + stmt.params.version = a[2]; + stmt.params.active = a[3]; + stmt.params.userDisabled = a[4]; + stmt.params.installDate = a[5]; + stmt.execute(); + internal_ids[a[0]] = db.lastInsertRowID; + }); + stmt.finalize(); + + db.schemaVersion = 15; + Services.prefs.setIntPref("extensions.databaseSchema", 14); + db.close(); + + startupManager(); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", + function check_before_rebuild (a1) { + // First check that it migrated OK once + // addon1 was disabled in the database + do_check_neq(a1, null); + do_check_true(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_false(a1.isActive); + do_check_false(a1.strictCompatibility); + do_check_false(a1.foreignInstall); + + run_next_test(); + }); +} + +// now shut down, remove the JSON database, +// start up again, and make sure the data didn't migrate this time +add_test(function rebuild_again() { + shutdownManager(); + gExtensionsJSON.remove(true); + startupManager(); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", + function check_after_rebuild(a1) { + // addon1 was rebuilt from extensions directory, + // so it appears enabled as a foreign install + do_check_neq(a1, null); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_true(a1.isActive); + do_check_false(a1.strictCompatibility); + do_check_true(a1.foreignInstall); + + run_next_test(); + }); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 6113454b47a2..982aa9705982 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -205,6 +205,7 @@ skip-if = os == "android" [test_migrate4.js] [test_migrate5.js] [test_migrateAddonRepository.js] +[test_migrate_max_version.js] [test_onPropertyChanged_appDisabled.js] [test_permissions.js] [test_plugins.js] From dbc1a19fe147c191352306b895f09ad8ec9c3d5b Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Thu, 8 Aug 2013 16:05:33 -0400 Subject: [PATCH 15/40] Bug 853388: Load JSON database asynchronously outside of startup; r=unfocused --- toolkit/mozapps/extensions/XPIProvider.jsm | 3 +- .../mozapps/extensions/XPIProviderUtils.js | 478 +++++++++++------- .../test/xpcshell/test_migrate_max_version.js | 27 +- 3 files changed, 312 insertions(+), 196 deletions(-) diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 83adbdc628b6..61ef77ed3fc7 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -3218,9 +3218,8 @@ var XPIProvider = { // If the database needs to be updated then open it and then update it // from the filesystem if (updateDatabase || hasPendingChanges) { + XPIDatabase.syncLoadDB(false); try { - XPIDatabase.openConnection(false, true); - extensionListChanged = this.processFileChanges(state, manifests, aAppChanged, aOldAppVersion, diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 9147cb1b448e..89ce9fac9c0d 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -20,6 +20,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave", "resource://gre/modules/DeferredSave.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); ["LOG", "WARN", "ERROR"].forEach(function(aName) { Object.defineProperty(this, aName, { @@ -158,6 +160,20 @@ function getRepositoryAddon(aAddon, aCallback) { AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); } +/** + * Wrap an API-supplied function in an exception handler to make it safe to call + */ +function safeCallback(aCallback) { + return function(...aArgs) { + try { + aCallback.apply(null, aArgs); + } + catch(ex) { + WARN("XPI Database callback failed", ex); + } + } +} + /** * A helper method to asynchronously call a function on an array * of objects, calling a callback when function(x) has been gathered @@ -375,6 +391,32 @@ DBAddonInternal.prototype = { DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; +/** + * Internal interface: find an addon from an already loaded addonDB + */ +function _findAddon(addonDB, aFilter) { + for (let [, addon] of addonDB) { + if (aFilter(addon)) { + return addon; + } + } + return null; +} + +/** + * Internal interface to get a filtered list of addons from a loaded addonDB + */ +function _filterDB(addonDB, aFilter) { + let addonList = []; + for (let [, addon] of addonDB) { + if (aFilter(addon)) { + addonList.push(addon); + } + } + + return addonList; +} + this.XPIDatabase = { // true if the database connection has been opened initialized: false, @@ -410,6 +452,12 @@ this.XPIDatabase = { return; } + if (!this._deferredSave) { + this._deferredSave = new DeferredSave(this.jsonFile.path, + () => JSON.stringify(this), + ASYNC_SAVE_DELAY_MS); + } + let promise = this._deferredSave.saveChanges(); if (!this._schemaVersionSet) { this._schemaVersionSet = true; @@ -429,8 +477,8 @@ this.XPIDatabase = { }, flush: function() { - // handle the "in memory only" case - if (this.lockedDatabase) { + // handle the "in memory only" and "saveChanges never called" cases + if (!this._deferredSave) { let done = Promise.defer(); done.resolve(0); return done.promise; @@ -439,19 +487,17 @@ this.XPIDatabase = { return this._deferredSave.flush(); }, - get _deferredSave() { - delete this._deferredSave; - return this._deferredSave = - new DeferredSave(this.jsonFile.path, () => JSON.stringify(this), - ASYNC_SAVE_DELAY_MS); - }, - /** * Converts the current internal state of the XPI addon database to JSON */ toJSON: function() { + if (!this.addonDB) { + // We never loaded the database? + throw new Error("Attempt to save database without loading it first"); + } + let addons = []; - for (let [key, addon] of this.addonDB) { + for (let [, addon] of this.addonDB) { addons.push(addon); } let toSave = { @@ -489,7 +535,7 @@ this.XPIDatabase = { }, /** - * Opens and reads the database file, upgrading from old + * Synchronously opens and reads the database file, upgrading from old * databases or making a new DB if needed. * * The possibilities, in order of priority, are: @@ -506,10 +552,8 @@ this.XPIDatabase = { * from the install locations if the database needs to be rebuilt. * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ - openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) { - // XXX TELEMETRY report opens with aRebuildOnError true (which implies delayed open) - // vs. aRebuildOnError false (DB loaded during startup) - delete this.addonDB; + syncLoadDB: function XPIDB_syncLoadDB(aRebuildOnError) { + // XXX TELEMETRY report synchronous opens (startup time) vs. delayed opens this.migrateData = null; let fstream = null; let data = ""; @@ -530,42 +574,10 @@ this.XPIDatabase = { data += str.value; } while (read != 0); } - // dump("Loaded JSON:\n" + data + "\n"); - let inputAddons = JSON.parse(data); - // Now do some sanity checks on our JSON db - if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { - // Content of JSON file is bad, need to rebuild from scratch - ERROR("bad JSON file contents"); - this.rebuildDatabase(aRebuildOnError); - } - if (inputAddons.schemaVersion != DB_SCHEMA) { - // Handle mismatched JSON schema version. For now, we assume - // compatibility for JSON data, though we throw away any fields we - // don't know about - // XXX preserve unknown fields during save/restore - LOG("JSON schema mismatch: expected " + DB_SCHEMA + - ", actual " + inputAddons.schemaVersion); - } - // If we got here, we probably have good data - // Make AddonInternal instances from the loaded data and save them - let addonDB = new Map(); - inputAddons.addons.forEach(function(loadedAddon) { - let newAddon = new DBAddonInternal(loadedAddon); - addonDB.set(newAddon._key, newAddon); - }); - this.addonDB = addonDB; - LOG("Successfully read XPI database"); - this.initialized = true; + this.parseDB(data, aRebuildOnError); } catch(e) { - // If we catch and log a SyntaxError from the JSON - // parser, the xpcshell test harness fails the test for us: bug 870828 - if (e.name == "SyntaxError") { - ERROR("Syntax error parsing saved XPI JSON data"); - } - else { - ERROR("Failed to load XPI JSON data from profile", e); - } + ERROR("Failed to load XPI JSON data from profile", e); this.rebuildDatabase(aRebuildOnError); } finally { @@ -575,46 +587,151 @@ this.XPIDatabase = { } catch (e) { if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { - try { - let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); - if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { - // we should have an older SQLITE database - this.migrateData = this.getMigrateDataFromSQLITE(); - } - // else we've upgraded before but the JSON file is gone, fall through - // and rebuild from scratch - } - catch(e) { - // No schema version pref means either a really old upgrade (RDF) or - // a new profile - this.migrateData = this.getMigrateDataFromRDF(); - } - - this.rebuildDatabase(aRebuildOnError); + this.upgradeDB(aRebuildOnError); } else { - WARN("Extensions database " + this.jsonFile.path + - " exists but is not readable; rebuilding in memory", e); - // XXX open question - if we can overwrite at save time, should we, or should we - // leave the locked database in case we can recover from it next time we start up? - // The old code made one attempt to remove the locked file before it rebuilt in memory - this.lockedDatabase = true; - // XXX TELEMETRY report when this happens? - this.rebuildDatabase(aRebuildOnError); + this.rebuildUnreadableDB(e, aRebuildOnError); } } finally { if (fstream) fstream.close(); } + // If an async load was also in progress, resolve that promise with our DB; + // otherwise create a resolved promise + if (this._dbPromise) + this._dbPromise.resolve(this.addonDB); + else + this._dbPromise = Promise.resolve(this.addonDB); + }, - return; - - // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"? - if (!aForceOpen && !this.dbfileExists) { - this.connection = null; - return; + /** + * Parse loaded data, reconstructing the database if the loaded data is not valid + * @param aRebuildOnError + * If true, synchronously reconstruct the database from installed add-ons + */ + parseDB: function(aData, aRebuildOnError) { + try { + // dump("Loaded JSON:\n" + aData + "\n"); + let inputAddons = JSON.parse(aData); + // Now do some sanity checks on our JSON db + if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { + // Content of JSON file is bad, need to rebuild from scratch + ERROR("bad JSON file contents"); + this.rebuildDatabase(aRebuildOnError); + return; + } + if (inputAddons.schemaVersion != DB_SCHEMA) { + // Handle mismatched JSON schema version. For now, we assume + // compatibility for JSON data, though we throw away any fields we + // don't know about + // XXX preserve unknown fields during save/restore + LOG("JSON schema mismatch: expected " + DB_SCHEMA + + ", actual " + inputAddons.schemaVersion); + } + // If we got here, we probably have good data + // Make AddonInternal instances from the loaded data and save them + let addonDB = new Map(); + inputAddons.addons.forEach(function(loadedAddon) { + let newAddon = new DBAddonInternal(loadedAddon); + addonDB.set(newAddon._key, newAddon); + }); + this.addonDB = addonDB; + LOG("Successfully read XPI database"); + this.initialized = true; } + catch(e) { + // If we catch and log a SyntaxError from the JSON + // parser, the xpcshell test harness fails the test for us: bug 870828 + if (e.name == "SyntaxError") { + ERROR("Syntax error parsing saved XPI JSON data"); + } + else { + ERROR("Failed to load XPI JSON data from profile", e); + } + this.rebuildDatabase(aRebuildOnError); + } + }, + + /** + * Upgrade database from earlier (sqlite or RDF) version if available + */ + upgradeDB: function(aRebuildOnError) { + try { + let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); + if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { + // we should have an older SQLITE database + this.migrateData = this.getMigrateDataFromSQLITE(); + } + // else we've upgraded before but the JSON file is gone, fall through + // and rebuild from scratch + } + catch(e) { + // No schema version pref means either a really old upgrade (RDF) or + // a new profile + this.migrateData = this.getMigrateDataFromRDF(); + } + + this.rebuildDatabase(aRebuildOnError); + }, + + /** + * Reconstruct when the DB file exists but is unreadable + * (for example because read permission is denied + */ + rebuildUnreadableDB: function(aError, aRebuildOnError) { + WARN("Extensions database " + this.jsonFile.path + + " exists but is not readable; rebuilding in memory", aError); + // XXX open question - if we can overwrite at save time, should we, or should we + // leave the locked database in case we can recover from it next time we start up? + // The old code made one attempt to remove the locked file before it rebuilt in memory + this.lockedDatabase = true; + // XXX TELEMETRY report when this happens? + this.rebuildDatabase(aRebuildOnError); + }, + + /** + * Open and read the XPI database asynchronously, upgrading if + * necessary. If any DB load operation fails, we need to + * synchronously rebuild the DB from the installed extensions. + * + * @return Promise resolves to the Map of loaded JSON data stored + * in this.addonDB; never rejects. + */ + asyncLoadDB: function XPIDB_asyncLoadDB(aDBCallback) { + // Already started (and possibly finished) loading + if (this._dbPromise) { + return this._dbPromise; + } + + LOG("Starting async load of XPI database " + this.jsonFile.path); + return this._dbPromise = OS.File.read(this.jsonFile.path).then( + byteArray => { + if (this._addonDB) { + LOG("Synchronous load completed while waiting for async load"); + return this.addonDB; + } + LOG("Finished async read of XPI database, parsing..."); + let decoder = new TextDecoder(); + let data = decoder.decode(byteArray); + this.parseDB(data, true); + return this.addonDB; + }) + .then(null, + error => { + if (this._addonDB) { + LOG("Synchronous load completed while waiting for async load"); + return this.addonDB; + } + if (error.becauseNoSuchFile) { + this.upgradeDB(true); + } + else { + // it's there but unreadable + this.rebuildUnreadableDB(error, true); + } + return this.addonDB; + }); }, /** @@ -649,21 +766,13 @@ this.XPIDatabase = { } }, - /** - * Lazy getter for the addons database - */ - get addonDB() { - this.openConnection(true); - return this.addonDB; - }, - /** * Gets the list of file descriptors of active extension directories or XPI * files from the add-ons list. This must be loaded from disk since the * directory service gives no easy way to get both directly. This list doesn't * include themes as preferences already say which theme is currently active * - * @return an array of persisitent descriptors for the directories + * @return an array of persistent descriptors for the directories */ getActiveBundles: function XPIDB_getActiveBundles() { let bundles = []; @@ -882,27 +991,11 @@ this.XPIDatabase = { return 0; }) .then(count => { - // Clear out the cached addons data loaded from JSON and recreate - // the getter to allow database re-loads during testing. + // Clear out the cached addons data loaded from JSON delete this.addonDB; - Object.defineProperty(this, "addonDB", { - get: function addonsGetter() { - this.openConnection(true); - return this.addonDB; - }, - configurable: true - }); + delete this._dbPromise; // same for the deferred save delete this._deferredSave; - Object.defineProperty(this, "_deferredSave", { - set: function deferredSaveGetter() { - delete this._deferredSave; - return this._deferredSave = - new DeferredSave(this.jsonFile.path, this.formJSON.bind(this), - ASYNC_SAVE_DELAY_MS); - }, - configurable: true - }); // re-enable the schema version setter delete this._schemaVersionSet; @@ -920,7 +1013,8 @@ this.XPIDatabase = { * Return a list of all install locations known about by the database. This * is often a a subset of the total install locations when not all have * installed add-ons, occasionally a superset when an install location no - * longer exists. + * longer exists. Only called from XPIProvider.processFileChanges, when + * the database should already be loaded. * * @return a Set of names of install locations */ @@ -936,61 +1030,62 @@ this.XPIDatabase = { }, /** - * List all addons that match the filter function + * Asynchronously list all addons that match the filter function * @param aFilter * Function that takes an addon instance and returns * true if that addon should be included in the selected array - * @return an array of DBAddonInternals + * @param aCallback + * Called back with an array of addons matching aFilter + * or an empty array if none match */ - _listAddons: function XPIDB_listAddons(aFilter) { - if (!this.addonDB) - return []; - - let addonList = []; - for (let [key, addon] of this.addonDB) { - if (aFilter(addon)) { - addonList.push(addon); - } - } - - return addonList; + getAddonList: function(aFilter, aCallback) { + this.asyncLoadDB().then( + addonDB => { + let addonList = _filterDB(addonDB, aFilter); + asyncMap(addonList, getRepositoryAddon, safeCallback(aCallback)); + }) + .then(null, + error => { + ERROR("getAddonList failed", e); + safeCallback(aCallback)([]); + }); }, /** - * Find the first addon that matches the filter function + * (Possibly asynchronously) get the first addon that matches the filter function * @param aFilter * Function that takes an addon instance and returns * true if that addon should be selected - * @return The first DBAddonInternal for which the filter returns true + * @param aCallback + * Called back with the addon, or null if no matching addon is found */ - _findAddon: function XPIDB_findAddon(aFilter) { - if (!this.addonDB) - return null; - - for (let [key, addon] of this.addonDB) { - if (aFilter(addon)) { - return addon; - } - } - - return null; + getAddon: function(aFilter, aCallback) { + return this.asyncLoadDB().then( + addonDB => { + getRepositoryAddon(_findAddon(addonDB, aFilter), safeCallback(aCallback)); + }) + .then(null, + error => { + ERROR("getAddon failed", e); + safeCallback(aCallback)(null); + }); }, /** * Synchronously reads all the add-ons in a particular install location. + * Always called with the addon database already loaded. * * @param aLocation * The name of the install location * @return an array of DBAddonInternals */ getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) { - return this._listAddons(function inLocation(aAddon) {return (aAddon.location == aLocation);}); + return _filterDB(this.addonDB, aAddon => (aAddon.location == aLocation)); }, /** * Asynchronously gets an add-on with a particular ID in a particular * install location. - * XXX IRVING sync for now * * @param aId * The ID of the add-on to retrieve @@ -1000,12 +1095,13 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { - getRepositoryAddon(this.addonDB.get(aLocation + ":" + aId), aCallback); + this.asyncLoadDB().then( + addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId), + safeCallback(aCallback))); }, /** - * Asynchronously gets the add-on with an ID that is visible. - * XXX IRVING sync + * Asynchronously gets the add-on with the specified ID that is visible. * * @param aId * The ID of the add-on to retrieve @@ -1013,13 +1109,12 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) { - let addon = this._findAddon(function visibleID(aAddon) {return ((aAddon.id == aId) && aAddon.visible)}); - getRepositoryAddon(addon, aCallback); + this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible), + aCallback); }, /** * Asynchronously gets the visible add-ons, optionally restricting by type. - * XXX IRVING sync * * @param aTypes * An array of types to include or null to include all types @@ -1027,10 +1122,10 @@ this.XPIDatabase = { * A callback to pass the array of DBAddonInternals to */ getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) { - let addons = this._listAddons(function visibleType(aAddon) { - return (aAddon.visible && (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) - }); - asyncMap(addons, getRepositoryAddon, aCallback); + this.getAddonList(aAddon => (aAddon.visible && + (!aTypes || (aTypes.length == 0) || + (aTypes.indexOf(aAddon.type) > -1))), + aCallback); }, /** @@ -1041,7 +1136,14 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddonsByType: function XPIDB_getAddonsByType(aType) { - return this._listAddons(function byType(aAddon) { return aAddon.type == aType; }); + if (!this.addonDB) { + // jank-tastic! Must synchronously load DB if the theme switches from + // an XPI theme to a lightweight theme before the DB has loaded, + // because we're called from sync XPIProvider.addonChanged + WARN("Synchronous load of XPI database due to getAddonsByType(" + aType + ")"); + this.syncLoadDB(true); + } + return _filterDB(this.addonDB, aAddon => (aAddon.type == aType)); }, /** @@ -1052,14 +1154,20 @@ this.XPIDatabase = { * @return a DBAddonInternal */ getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) { - return this._findAddon(function visibleInternalName(aAddon) { - return (aAddon.visible && (aAddon.internalName == aInternalName)); - }); + if (!this.addonDB) { + // This may be called when the DB hasn't otherwise been loaded + // XXX TELEMETRY + WARN("Synchronous load of XPI database due to getVisibleAddonForInternalName"); + this.syncLoadDB(true); + } + + return _findAddon(this.addonDB, + aAddon => aAddon.visible && + (aAddon.internalName == aInternalName)); }, /** * Asynchronously gets all add-ons with pending operations. - * XXX IRVING sync * * @param aTypes * The types of add-ons to retrieve or null to get all types @@ -1069,21 +1177,19 @@ this.XPIDatabase = { getVisibleAddonsWithPendingOperations: function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) { - let addons = this._listAddons(function visibleType(aAddon) { - return (aAddon.visible && - (aAddon.pendingUninstall || - // Logic here is tricky. If we're active but either - // disabled flag is set, we're pending disable; if we're not - // active and neither disabled flag is set, we're pending enable - (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && - (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) - }); - asyncMap(addons, getRepositoryAddon, aCallback); + this.getAddonList( + aAddon => (aAddon.visible && + (aAddon.pendingUninstall || + // Logic here is tricky. If we're active but either + // disabled flag is set, we're pending disable; if we're not + // active and neither disabled flag is set, we're pending enable + (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && + (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))), + aCallback); }, /** * Asynchronously get an add-on by its Sync GUID. - * XXX IRVING sync * * @param aGUID * Sync GUID of add-on to fetch @@ -1093,17 +1199,23 @@ this.XPIDatabase = { * */ getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) { - let addon = this._findAddon(function bySyncGUID(aAddon) { return aAddon.syncGUID == aGUID; }); - getRepositoryAddon(addon, aCallback); + this.getAddon(aAddon => aAddon.syncGUID == aGUID, + aCallback); }, /** * Synchronously gets all add-ons in the database. + * This is only called from the preference observer for the default + * compatibility version preference, so we can return an empty list if + * we haven't loaded the database yet. * * @return an array of DBAddonInternals */ getAddons: function XPIDB_getAddons() { - return this._listAddons(function(aAddon) {return true;}); + if (!this.addonDB) { + return []; + } + return _filterDB(this.addonDB, aAddon => true); }, /** @@ -1116,12 +1228,10 @@ this.XPIDatabase = { * @return The DBAddonInternal that was added to the database */ addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) { - // If there is no DB yet then forcibly create one - // XXX IRVING I don't think this will work as expected because the addonDB - // getter will kick in. Might not matter because of the way the new DB - // creates itself. - if (!this.addonDB) - this.openConnection(false, true); + if (!this.addonDB) { + // XXX telemetry. Should never happen on platforms that have a default theme + this.syncLoadDB(false); + } let newAddon = new DBAddonInternal(aAddon); newAddon.descriptor = aDescriptor; @@ -1182,7 +1292,7 @@ this.XPIDatabase = { */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { LOG("Make addon " + aAddon._key + " visible"); - for (let [key, otherAddon] of this.addonDB) { + for (let [, otherAddon] of this.addonDB) { if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { LOG("Hide addon " + otherAddon._key); otherAddon.visible = false; @@ -1209,6 +1319,7 @@ this.XPIDatabase = { /** * Synchronously sets the Sync GUID for an add-on. + * Only called when the database is already loaded. * * @param aAddon * The DBAddonInternal being updated @@ -1221,7 +1332,7 @@ this.XPIDatabase = { function excludeSyncGUID(otherAddon) { return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID); } - let otherAddon = this._findAddon(excludeSyncGUID); + let otherAddon = _findAddon(this.addonDB, excludeSyncGUID); if (otherAddon) { throw new Error("Addon sync GUID conflict for addon " + aAddon._key + ": " + otherAddon._key + " already has GUID " + aGUID); @@ -1264,7 +1375,7 @@ this.XPIDatabase = { // XXX IRVING this may get called during XPI-utils shutdown // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); - for (let [key, addon] of this.addonDB) { + for (let [, addon] of this.addonDB) { let newActive = (addon.visible && !addon.userDisabled && !addon.softDisabled && !addon.appDisabled && !addon.pendingUninstall); @@ -1279,6 +1390,10 @@ this.XPIDatabase = { * Writes out the XPI add-ons list for the platform to read. */ writeAddonsList: function XPIDB_writeAddonsList() { + if (!this.addonDB) { + // Unusual condition, force the DB to load + this.syncLoadDB(true); + } Services.appinfo.invalidateCachesOnRestart(); let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], @@ -1288,9 +1403,9 @@ this.XPIDatabase = { let count = 0; let fullCount = 0; - let activeAddons = this._listAddons(function active(aAddon) { - return aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme"); - }); + let activeAddons = _filterDB( + this.addonDB, + aAddon => aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme")); for (let row of activeAddons) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; @@ -1310,12 +1425,13 @@ this.XPIDatabase = { let themes = []; if (dssEnabled) { - themes = this._listAddons(function isTheme(aAddon){ return aAddon.type == "theme"; }); + themes = _filterDB(this.addonDB, aAddon => aAddon.type == "theme"); } else { - let activeTheme = this._findAddon(function isSelected(aAddon) { - return ((aAddon.type == "theme") && (aAddon.internalName == XPIProvider.selectedSkin)); - }); + let activeTheme = _findAddon( + this.addonDB, + aAddon => (aAddon.type == "theme") && + (aAddon.internalName == XPIProvider.selectedSkin)); if (activeTheme) { themes.push(activeTheme); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js index 4f52cf6ca6b5..133c3a1990eb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js @@ -45,25 +45,26 @@ function run_test() { let internal_ids = {}; - [["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"] - ].forEach(function(a) { - stmt.params.id = a[0]; - stmt.params.location = a[1]; - stmt.params.version = a[2]; - stmt.params.active = a[3]; - stmt.params.userDisabled = a[4]; - stmt.params.installDate = a[5]; - stmt.execute(); - internal_ids[a[0]] = db.lastInsertRowID; - }); + let a = ["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"]; + stmt.params.id = a[0]; + stmt.params.location = a[1]; + stmt.params.version = a[2]; + stmt.params.active = a[3]; + stmt.params.userDisabled = a[4]; + stmt.params.installDate = a[5]; + stmt.execute(); + internal_ids[a[0]] = db.lastInsertRowID; stmt.finalize(); - db.schemaVersion = 15; + db.schemaVersion = 14; Services.prefs.setIntPref("extensions.databaseSchema", 14); db.close(); startupManager(); + run_next_test(); +} +add_test(function before_rebuild() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function check_before_rebuild (a1) { // First check that it migrated OK once @@ -77,7 +78,7 @@ function run_test() { run_next_test(); }); -} +}); // now shut down, remove the JSON database, // start up again, and make sure the data didn't migrate this time From 06a5aa073c1a9f5de2030c644a812e69a0c2af14 Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Thu, 8 Aug 2013 16:08:42 -0400 Subject: [PATCH 16/40] Bug 853388: Keep trying to save JSON even if read or save fails; r=unfocused --- toolkit/mozapps/extensions/DeferredSave.jsm | 6 +- toolkit/mozapps/extensions/XPIProvider.jsm | 9 +- .../mozapps/extensions/XPIProviderUtils.js | 92 ++-- .../extensions/test/xpcshell/head_addons.js | 25 +- .../xpcshell/test_ChromeManifestParser.js | 2 +- .../test/xpcshell/test_DeferredSave.js | 8 +- .../test/xpcshell/test_blocklistchange.js | 13 +- .../test/xpcshell/test_bootstrap.js | 91 ++-- .../test/xpcshell/test_bug324121.js | 4 +- .../test/xpcshell/test_bug335238.js | 4 +- .../test/xpcshell/test_bug371495.js | 2 +- .../test/xpcshell/test_bug384052.js | 2 +- .../test/xpcshell/test_bug397778.js | 16 +- .../test/xpcshell/test_bug425657.js | 2 +- .../test/xpcshell/test_bug455906.js | 12 +- .../test/xpcshell/test_bug470377_3.js | 2 +- .../xpcshell/test_bug470377_3_strictcompat.js | 2 +- .../test/xpcshell/test_bug470377_4.js | 2 +- .../test/xpcshell/test_bug521905.js | 2 +- .../test/xpcshell/test_bug526598.js | 6 +- .../test/xpcshell/test_bug541420.js | 2 +- .../test/xpcshell/test_bug542391.js | 8 +- .../test/xpcshell/test_bug559800.js | 8 +- .../test/xpcshell/test_bug563256.js | 6 +- .../test/xpcshell/test_bug564030.js | 6 +- .../test/xpcshell/test_bug566626.js | 2 +- .../test/xpcshell/test_bug567184.js | 2 +- .../test/xpcshell/test_bug569138.js | 2 +- .../test/xpcshell/test_bug576735.js | 6 +- .../test/xpcshell/test_bug587088.js | 22 +- .../test/xpcshell/test_bug595573.js | 2 +- .../test/xpcshell/test_bug596607.js | 2 +- .../test/xpcshell/test_bug616841.js | 2 +- .../test/xpcshell/test_bug655254.js | 10 +- .../test/xpcshell/test_bug659772.js | 4 +- .../test/xpcshell/test_bug675371.js | 6 +- .../test/xpcshell/test_bug740612.js | 2 +- .../test/xpcshell/test_bug753900.js | 2 +- .../test/xpcshell/test_bug757663.js | 2 +- .../test/xpcshell/test_cacheflush.js | 2 +- .../test/xpcshell/test_checkcompatibility.js | 2 +- .../test/xpcshell/test_dictionary.js | 42 +- .../extensions/test/xpcshell/test_disable.js | 18 +- .../test/xpcshell/test_distribution.js | 6 +- .../extensions/test/xpcshell/test_dss.js | 21 +- .../test/xpcshell/test_duplicateplugins.js | 6 +- .../extensions/test/xpcshell/test_error.js | 2 +- .../test/xpcshell/test_filepointer.js | 34 +- .../extensions/test/xpcshell/test_fuel.js | 2 +- .../extensions/test/xpcshell/test_general.js | 7 +- .../test/xpcshell/test_getresource.js | 7 +- .../test/xpcshell/test_hasbinarycomponents.js | 2 +- .../extensions/test/xpcshell/test_hotfix.js | 16 +- .../extensions/test/xpcshell/test_install.js | 78 ++-- .../test/xpcshell/test_install_icons.js | 2 +- .../xpcshell/test_install_strictcompat.js | 36 +- .../extensions/test/xpcshell/test_langpack.js | 22 +- .../extensions/test/xpcshell/test_locale.js | 27 +- .../extensions/test/xpcshell/test_locked.js | 378 +++++++++-------- .../extensions/test/xpcshell/test_locked2.js | 227 +++++----- .../test/xpcshell/test_locked_strictcompat.js | 397 ++++++++++-------- .../extensions/test/xpcshell/test_manifest.js | 2 +- .../test/xpcshell/test_mapURIToAddonID.js | 6 +- .../extensions/test/xpcshell/test_migrate1.js | 2 +- .../extensions/test/xpcshell/test_migrate2.js | 2 +- .../extensions/test/xpcshell/test_migrate3.js | 2 +- .../extensions/test/xpcshell/test_migrate5.js | 2 +- .../test_onPropertyChanged_appDisabled.js | 2 +- .../test/xpcshell/test_pluginchange.js | 2 +- .../extensions/test/xpcshell/test_plugins.js | 2 +- .../extensions/test/xpcshell/test_registry.js | 2 +- .../extensions/test/xpcshell/test_safemode.js | 6 +- .../extensions/test/xpcshell/test_startup.js | 6 +- .../extensions/test/xpcshell/test_syncGUID.js | 4 +- .../test/xpcshell/test_targetPlatforms.js | 2 +- .../extensions/test/xpcshell/test_theme.js | 38 +- .../test/xpcshell/test_uninstall.js | 8 +- .../extensions/test/xpcshell/test_update.js | 30 +- .../test/xpcshell/test_update_strictcompat.js | 14 +- .../extensions/test/xpcshell/test_updateid.js | 33 +- .../extensions/test/xpcshell/test_upgrade.js | 2 +- .../xpcshell/test_upgrade_strictcompat.js | 2 +- 82 files changed, 994 insertions(+), 907 deletions(-) mode change 100644 => 100755 toolkit/mozapps/extensions/test/xpcshell/test_locked.js diff --git a/toolkit/mozapps/extensions/DeferredSave.jsm b/toolkit/mozapps/extensions/DeferredSave.jsm index 20a75fe83203..298a4f3a5609 100644 --- a/toolkit/mozapps/extensions/DeferredSave.jsm +++ b/toolkit/mozapps/extensions/DeferredSave.jsm @@ -73,10 +73,14 @@ function DeferredSave(aPath, aDataProvider, aDelay) { // Some counters for telemetry // The total number of times the file was written this.totalSaves = 0; + // The number of times the data became dirty while // another save was in progress this.overlappedSaves = 0; + // Error returned by the most recent write (if any) + this._lastError = null; + if (aDelay && (aDelay > 0)) this._delay = aDelay; else @@ -88,7 +92,7 @@ DeferredSave.prototype = { return this._pending || this.writeInProgress; }, - get error() { + get lastError() { return this._lastError; }, diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 61ef77ed3fc7..98cbeef518f8 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -1884,9 +1884,9 @@ var XPIProvider = { delete this._uriMappings; if (gLazyObjectsLoaded) { - XPIDatabase.shutdown(function shutdownCallback() { + XPIDatabase.shutdown(function shutdownCallback(saveError) { LOG("Notifying XPI shutdown observers"); - Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); + Services.obs.notifyObservers(null, "xpi-provider-shutdown", saveError); }); } else { @@ -2594,9 +2594,8 @@ var XPIProvider = { aOldAddon.descriptor = aAddonState.descriptor; aOldAddon.visible = !(aOldAddon.id in visibleAddons); + XPIDatabase.saveChanges(); - // Update the database - XPIDatabase.setAddonDescriptor(aOldAddon, aAddonState.descriptor); if (aOldAddon.visible) { visibleAddons[aOldAddon.id] = aOldAddon; @@ -3188,8 +3187,6 @@ var XPIProvider = { // If the database doesn't exist and there are add-ons installed then we // must update the database however if there are no add-ons then there is // no need to update the database. - // Avoid using XPIDatabase.dbFileExists, as that code is lazy-loaded, - // and we want to avoid loading it until absolutely necessary. let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); if (!dbFile.exists()) updateDatabase = state.length > 0; diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 89ce9fac9c0d..ad36119d54bd 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -349,7 +349,6 @@ function DBAddonInternal(aLoaded) { // this change is being detected. } - // XXX Can we redesign pendingUpgrade? XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", function DBA_pendingUpgradeGetter() { for (let install of XPIProvider.installs) { @@ -426,17 +425,17 @@ this.XPIDatabase = { migrateData: null, // Active add-on directories loaded from extensions.ini and prefs at startup. activeBundles: null, - // Special handling for when the database is locked at first load - lockedDatabase: false, - // XXX may be able to refactor this away - get dbfileExists() { - delete this.dbfileExists; - return this.dbfileExists = this.jsonFile.exists(); - }, - set dbfileExists(aValue) { - delete this.dbfileExists; - return this.dbfileExists = aValue; + // Saved error object if we fail to read an existing database + _loadError: null, + + // Error reported by our most recent attempt to read or write the database, if any + get lastError() { + if (this._loadError) + return this._loadError; + if (this._deferredSave) + return this._deferredSave.lastError; + return null; }, /** @@ -447,11 +446,6 @@ this.XPIDatabase = { throw new Error("Attempt to use XPI database when it is not initialized"); } - // handle the "in memory only" case - if (this.lockedDatabase) { - return; - } - if (!this._deferredSave) { this._deferredSave = new DeferredSave(this.jsonFile.path, () => JSON.stringify(this), @@ -467,11 +461,16 @@ this.XPIDatabase = { // save the database. LOG("XPI Database saved, setting schema version preference to " + DB_SCHEMA); Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); + // Reading the DB worked once, so we don't need the load error + this._loadError = null; }, error => { // Need to try setting the schema version again later this._schemaVersionSet = false; WARN("Failed to save XPI database", error); + // this._deferredSave.lastError has the most recent error so we don't + // need this any more + this._loadError = null; }); } }, @@ -479,16 +478,15 @@ this.XPIDatabase = { flush: function() { // handle the "in memory only" and "saveChanges never called" cases if (!this._deferredSave) { - let done = Promise.defer(); - done.resolve(0); - return done.promise; + return Promise.resolve(0); } return this._deferredSave.flush(); }, /** - * Converts the current internal state of the XPI addon database to JSON + * Converts the current internal state of the XPI addon database to + * a JSON.stringify()-ready structure */ toJSON: function() { if (!this.addonDB) { @@ -496,13 +494,9 @@ this.XPIDatabase = { throw new Error("Attempt to save database without loading it first"); } - let addons = []; - for (let [, addon] of this.addonDB) { - addons.push(addon); - } let toSave = { schemaVersion: DB_SCHEMA, - addons: addons + addons: [...this.addonDB.values()] }; return toSave; }, @@ -625,17 +619,16 @@ this.XPIDatabase = { // Handle mismatched JSON schema version. For now, we assume // compatibility for JSON data, though we throw away any fields we // don't know about - // XXX preserve unknown fields during save/restore LOG("JSON schema mismatch: expected " + DB_SCHEMA + ", actual " + inputAddons.schemaVersion); } // If we got here, we probably have good data // Make AddonInternal instances from the loaded data and save them let addonDB = new Map(); - inputAddons.addons.forEach(function(loadedAddon) { + for (let loadedAddon of inputAddons.addons) { let newAddon = new DBAddonInternal(loadedAddon); addonDB.set(newAddon._key, newAddon); - }); + }; this.addonDB = addonDB; LOG("Successfully read XPI database"); this.initialized = true; @@ -677,15 +670,14 @@ this.XPIDatabase = { /** * Reconstruct when the DB file exists but is unreadable - * (for example because read permission is denied + * (for example because read permission is denied) */ rebuildUnreadableDB: function(aError, aRebuildOnError) { WARN("Extensions database " + this.jsonFile.path + " exists but is not readable; rebuilding in memory", aError); - // XXX open question - if we can overwrite at save time, should we, or should we - // leave the locked database in case we can recover from it next time we start up? - // The old code made one attempt to remove the locked file before it rebuilt in memory - this.lockedDatabase = true; + // Remember the error message until we try and write at least once, so + // we know at shutdown time that there was a problem + this._loadError = aError; // XXX TELEMETRY report when this happens? this.rebuildDatabase(aRebuildOnError); }, @@ -973,24 +965,26 @@ this.XPIDatabase = { shutdown: function XPIDB_shutdown(aCallback) { LOG("shutdown"); if (this.initialized) { - // If we are running with an in-memory database then force a new - // extensions.ini to be written to disk on the next startup - if (this.lockedDatabase) - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + // If our last database I/O had an error, try one last time to save. + if (this.lastError) + this.saveChanges(); this.initialized = false; - let result = null; // Make sure any pending writes of the DB are complete, and we // finish cleaning up, and then call back this.flush() .then(null, error => { ERROR("Flush of XPI database failed", error); - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - result = error; return 0; }) .then(count => { + // If our last attempt to read or write the DB failed, force a new + // extensions.ini to be written to disk on the next startup + let lastSaveFailed = this.lastError; + if (lastSaveFailed) + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + // Clear out the cached addons data loaded from JSON delete this.addonDB; delete this._dbPromise; @@ -1000,7 +994,7 @@ this.XPIDatabase = { delete this._schemaVersionSet; if (aCallback) - aCallback(result); + aCallback(lastSaveFailed); }); } else { @@ -1341,20 +1335,6 @@ this.XPIDatabase = { this.saveChanges(); }, - /** - * Synchronously sets the file descriptor for an add-on. - * XXX IRVING could replace this with setAddonProperties - * - * @param aAddon - * The DBAddonInternal being updated - * @param aDescriptor - * File path of the installed addon - */ - setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { - aAddon.descriptor = aDescriptor; - this.saveChanges(); - }, - /** * Synchronously updates an add-on's active flag in the database. * @@ -1372,8 +1352,6 @@ this.XPIDatabase = { * Synchronously calculates and updates all the active flags in the database. */ updateActiveAddons: function XPIDB_updateActiveAddons() { - // XXX IRVING this may get called during XPI-utils shutdown - // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); for (let [, addon] of this.addonDB) { let newActive = (addon.visible && !addon.userDisabled && diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 781f38e21c30..877ce0b6c322 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -344,6 +344,10 @@ function do_check_icons(aActual, aExpected) { } } +// Record the error (if any) from trying to save the XPI +// database at shutdown time +let gXPISaveError = null; + /** * Starts up the add-on manager as if it was started by the application. * @@ -396,26 +400,24 @@ function shutdownManager() { if (!gInternalManager) return; - let obs = AM_Cc["@mozilla.org/observer-service;1"]. - getService(AM_Ci.nsIObserverService); - let xpiShutdown = false; - obs.addObserver({ + Services.obs.addObserver({ observe: function(aSubject, aTopic, aData) { xpiShutdown = true; - obs.removeObserver(this, "xpi-provider-shutdown"); + gXPISaveError = aData; + Services.obs.removeObserver(this, "xpi-provider-shutdown"); } }, "xpi-provider-shutdown", false); let repositoryShutdown = false; - obs.addObserver({ + Services.obs.addObserver({ observe: function(aSubject, aTopic, aData) { repositoryShutdown = true; - obs.removeObserver(this, "addon-repository-shutdown"); + Services.obs.removeObserver(this, "addon-repository-shutdown"); } }, "addon-repository-shutdown", false); - obs.notifyObservers(null, "quit-application-granted", null); + Services.obs.notifyObservers(null, "quit-application-granted", null); let scope = Components.utils.import("resource://gre/modules/AddonManager.jsm"); scope.AddonManagerInternal.shutdown(); gInternalManager = null; @@ -428,14 +430,11 @@ function shutdownManager() { // Clear any crash report annotations gAppInfo.annotations = {}; - let thr = AM_Cc["@mozilla.org/thread-manager;1"]. - getService(AM_Ci.nsIThreadManager). - mainThread; + let thr = Services.tm.mainThread; // Wait until we observe the shutdown notifications while (!repositoryShutdown || !xpiShutdown) { - if (thr.hasPendingEvents()) - thr.processNextEvent(false); + thr.processNextEvent(true); } // Force the XPIProvider provider to reload to better diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js b/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js index 61ffc12f6703..2e4adbe0f54d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js @@ -103,6 +103,6 @@ function run_test_1() { do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i])); } - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js b/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js index 7b29fa68d5c6..b40b5342fb23 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js @@ -37,8 +37,8 @@ function DeferredSaveTester(aDelay, aDataProvider) { return tester.saver.flush(); }, - get error() { - return tester.saver.error; + get lastError() { + return tester.saver.lastError; } }; @@ -154,11 +154,11 @@ add_task(function test_error_immediate() { count => do_throw("Did not get expected error"), error => do_check_eq(testError.message, error.message) ); - do_check_eq(testError, tester.error); + do_check_eq(testError, tester.lastError); // This write should succeed and clear the error yield tester.save("test_error_immediate succeeds"); - do_check_eq(null, tester.error); + do_check_eq(null, tester.lastError); // The failed save attempt counts in our total do_check_eq(2, tester.saver.totalSaves); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index d3b8aceb2ccb..752e4103740a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -335,12 +335,12 @@ const ADDON_IDS = ["softblock1@tests.mozilla.org", // Don't need the full interface, attempts to call other methods will just // throw which is just fine var WindowWatcher = { - openWindow: function(parent, url, name, features, arguments) { + openWindow: function(parent, url, name, features, openArgs) { // Should be called to list the newly blocklisted items do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG); // Simulate auto-disabling any softblocks - var list = arguments.wrappedJSObject.list; + var list = openArgs.wrappedJSObject.list; list.forEach(function(aItem) { if (!aItem.blocked) aItem.disable = true; @@ -1329,7 +1329,7 @@ add_test(function run_manual_update_2_test() { startupManager(false); - AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { + AddonManager.getAddonsByIDs(ADDON_IDS, callback_soon(function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1347,7 +1347,8 @@ add_test(function run_manual_update_2_test() { manual_update("2", function manual_update_2_2() { restartManager(); - AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { + AddonManager.getAddonsByIDs(ADDON_IDS, + callback_soon(function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1375,9 +1376,9 @@ add_test(function run_manual_update_2_test() { run_next_test(); }); }); - }); + })); }); - }); + })); }); // Uses the API to install blocked add-ons from the local filesystem diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index 912c676f8e14..b6b3c65f7aaa 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -455,7 +455,7 @@ function check_test_7() { do_check_eq(getShutdownNewVersion(), 0); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0"); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_eq(b1, null); restartManager(); @@ -465,7 +465,7 @@ function check_test_7() { do_check_bootstrappedPref(run_test_8); }); - }); + })); } // Test that a bootstrapped extension dropped into the profile loads properly @@ -671,12 +671,15 @@ function run_test_12() { do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); b1.uninstall(); - restartManager(); - - do_check_bootstrappedPref(run_test_13); + do_execute_soon(test_12_restart); }); } +function test_12_restart() { + restartManager(); + do_check_bootstrappedPref(run_test_13); +} + // Tests that installing a bootstrapped extension with an invalid application // entry doesn't call it's startup method @@ -703,7 +706,7 @@ function run_test_13() { }, [ "onInstallStarted", "onInstallEnded", - ], function() {do_execute_soon(check_test_13)}); + ], callback_soon(check_test_13)); install.install(); }); } @@ -724,23 +727,27 @@ function check_test_13() { do_check_eq(getActiveVersion(), 0); // Should not have called startup though do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); - restartManager(); + do_execute_soon(test_13_restart); + }); + }); +} - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { - do_check_neq(b1, null); - do_check_eq(b1.version, "3.0"); - do_check_true(b1.appDisabled); - do_check_false(b1.userDisabled); - do_check_false(b1.isActive); - do_check_eq(getInstalledVersion(), 3); // We call install even for disabled add-ons - do_check_eq(getActiveVersion(), 0); // Should not have called startup though - do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); +function test_13_restart() { + restartManager(); - do_check_bootstrappedPref(function() { - b1.uninstall(); - do_execute_soon(run_test_14); - }); - }); + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + do_check_neq(b1, null); + do_check_eq(b1.version, "3.0"); + do_check_true(b1.appDisabled); + do_check_false(b1.userDisabled); + do_check_false(b1.isActive); + do_check_eq(getInstalledVersion(), 3); // We call install even for disabled add-ons + do_check_eq(getActiveVersion(), 0); // Should not have called startup though + do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); + + do_check_bootstrappedPref(function() { + b1.uninstall(); + do_execute_soon(run_test_14); }); }); } @@ -812,7 +819,7 @@ function run_test_15() { }, [ "onInstallStarted", "onInstallEnded", - ], function() {do_execute_soon(check_test_15)}); + ], callback_soon(check_test_15)); install.install(); }); }); @@ -854,7 +861,7 @@ function check_test_15() { function run_test_16() { resetPrefs(); waitForPref("bootstraptest.startup_reason", function test_16_after_startup() { - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { // Should have installed and started do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); @@ -872,7 +879,7 @@ function run_test_16() { gAppInfo.inSafeMode = true; startupManager(false); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { // Should still be stopped do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 0); @@ -892,10 +899,10 @@ function run_test_16() { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { b1.uninstall(); - run_test_17(); + do_execute_soon(run_test_17); }); - }); - }); + })); + })); }); installAllFiles([do_get_addon("test_bootstrap1_1")], function() { }); } @@ -1022,7 +1029,7 @@ function run_test_20() { do_check_eq(getInstallOldVersion(), 1); do_check_eq(getStartupOldVersion(), 0); - run_test_21(); + do_execute_soon(run_test_21); }); } @@ -1079,7 +1086,7 @@ function run_test_22() { startupManager(); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { // Should have installed and started do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); @@ -1123,7 +1130,7 @@ function run_test_22() { run_test_23(); }); }); - }); + })); } @@ -1197,17 +1204,17 @@ function check_test_23() { let dir = do_get_addon_root_uri(profileDir, "bootstrap1@tests.mozilla.org"); do_check_eq(b1.getResourceURI("bootstrap.js").spec, dir + "bootstrap.js"); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 0); restartManager(); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { b1.uninstall(); restartManager(); testserver.stop(run_test_24); - }); - }); + })); + })); }); }); }); @@ -1274,7 +1281,7 @@ function run_test_25() { do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); @@ -1295,7 +1302,7 @@ function run_test_25() { do_check_bootstrappedPref(run_test_26); }); - }); + })); }); }); installAllFiles([do_get_addon("test_bootstrap1_1")], function test_25_installed() { @@ -1311,7 +1318,7 @@ function run_test_26() { do_check_eq(getInstalledVersion(), 0); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_true(b1.isActive); @@ -1332,7 +1339,7 @@ function run_test_26() { do_check_bootstrappedPref(run_test_27); }); - }); + })); }); } @@ -1355,7 +1362,7 @@ function run_test_27() { do_check_eq(getUninstallNewVersion(), 4); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_false(b1.isActive); @@ -1374,7 +1381,7 @@ function run_test_27() { do_check_bootstrappedPref(run_test_28); }); - }); + })); }); }); } @@ -1390,7 +1397,7 @@ function run_test_28() { do_check_eq(getInstallOldVersion(), 4); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_false(b1.isActive); @@ -1414,7 +1421,7 @@ function run_test_28() { do_check_bootstrappedPref(do_test_finished); }); - }); + })); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js index cb6ede5fc1f4..b88c07b235d9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js @@ -138,7 +138,7 @@ function run_test() { installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() { restartManager(); - AddonManager.getAddonByID(ADDONS[0].id, function(addon) { + AddonManager.getAddonByID(ADDONS[0].id, callback_soon(function(addon) { do_check_true(!(!addon)); addon.userDisabled = true; restartManager(); @@ -168,7 +168,7 @@ function run_test() { } } }); - }); + })); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js index c379fcdcdca3..8bfef2feffdc 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js @@ -165,7 +165,7 @@ function run_test() { installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() { restartManager(); - AddonManager.getAddonByID(ADDONS[1].id, function(addon) { + AddonManager.getAddonByID(ADDONS[1].id, callback_soon(function(addon) { do_check_true(!(!addon)); addon.userDisabled = true; restartManager(); @@ -176,6 +176,6 @@ function run_test() { item.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); }); - }); + })); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js index 7919b7c171fa..ac746dda7b19 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js @@ -28,7 +28,7 @@ function run_test() do_check_eq(addon.optionsURL, null); do_check_eq(addon.aboutURL, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js index bbdcb76a1b0c..aeaaf3d8ff2d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js @@ -96,7 +96,7 @@ function run_test() onUpdateFinished: function(addon, error) { do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); do_check_true(gSeenExpectedURL); - shutdownTest(); + do_execute_soon(shutdownTest); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js index 134b17a53bf5..aa18a6946ab9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js @@ -27,7 +27,7 @@ function run_test() } function run_test_1() { - AddonManager.getAddonByID(ID, function(addon) { + AddonManager.getAddonByID(ID, callback_soon(function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "fr Name"); do_check_eq(addon.description, "fr Description"); @@ -40,9 +40,9 @@ function run_test_1() { do_check_neq(newAddon, null); do_check_eq(newAddon.name, "fr Name"); - run_test_2(); + do_execute_soon(run_test_2); }); - }); + })); } function run_test_2() { @@ -55,7 +55,7 @@ function run_test_2() { do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - run_test_3(); + do_execute_soon(run_test_3); }); } @@ -69,7 +69,7 @@ function run_test_3() { do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - run_test_4(); + do_execute_soon(run_test_4); }); } @@ -83,7 +83,7 @@ function run_test_4() { do_check_eq(addon.name, "es-ES Name"); do_check_eq(addon.description, "es-ES Description"); - run_test_5(); + do_execute_soon(run_test_5); }); } @@ -97,7 +97,7 @@ function run_test_5() { if (addon.name != "zh-TW Name" && addon.name != "zh-CN Name") do_throw("zh matched to " + addon.name); - run_test_6(); + do_execute_soon(run_test_6); }); } @@ -112,6 +112,6 @@ function run_test_6() { do_check_eq(addon.name, "en Name"); do_check_eq(addon.description, "en Description"); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js index f383b201449a..f11a942fb68e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js @@ -21,7 +21,7 @@ function run_test() do_check_eq(addon.name, "Deutsches W\u00f6rterbuch"); do_check_eq(addon.name.length, 20); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js index fc31a4086229..4ed2df463c3b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js @@ -285,7 +285,7 @@ function check_initial_state(callback) { function check_test_pt1() { dump("Checking pt 1\n"); - AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) { + AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) { for (var i = 0; i < ADDONS.length; i++) { if (!addons[i]) do_throw("Addon " + (i + 1) + " did not get installed correctly"); @@ -316,7 +316,7 @@ function check_test_pt1() { gTestCheck = check_test_pt2; load_blocklist("bug455906_warn.xml"); }); - }); + })); } function check_notification_pt2(args) { @@ -357,7 +357,7 @@ function check_test_pt2() { restartManager(); dump("Checking results pt 2\n"); - AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) { + AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) { // Should have disabled this add-on as requested do_check_eq(check_addon_state(addons[2]), "true,true,false"); do_check_eq(check_plugin_state(PLUGINS[2]), "true,false"); @@ -386,7 +386,7 @@ function check_test_pt2() { gNotificationCheck = null; gTestCheck = run_test_pt3; load_blocklist("bug455906_start.xml"); - }); + })); } function run_test_pt3() { @@ -485,7 +485,7 @@ function check_test_pt3() { } function run_test_pt4() { - AddonManager.getAddonByID(ADDONS[4].id, function(addon) { + AddonManager.getAddonByID(ADDONS[4].id, callback_soon(function(addon) { addon.userDisabled = false; PLUGINS[4].enabledState = Ci.nsIPluginTag.STATE_ENABLED; restartManager(); @@ -494,7 +494,7 @@ function run_test_pt4() { gTestCheck = check_test_pt4; load_blocklist("bug455906_empty.xml"); }); - }); + })); } function check_notification_pt4(args) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js index 6dee8ee9e738..fcac471ee6db 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js @@ -90,6 +90,6 @@ function run_test_2() { do_check_neq(a5, null); do_check_true(a5.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js index e8adc8b07bc0..7a3347320e82 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js @@ -89,6 +89,6 @@ function run_test_2() { do_check_neq(a5, null); do_check_true(a5.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js index 362ccec5c452..701cbe4485ee 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js @@ -87,6 +87,6 @@ function run_test_2() { do_check_neq(a5, null); do_check_true(a5.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js index 962e1ebc1ec4..b507fc100125 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js @@ -54,6 +54,6 @@ function run_test_2() { do_check_neq(addon, null); do_check_false(addon.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js index f399dd9d114d..debf5917233e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js @@ -16,7 +16,7 @@ function run_test() { AddonManager.getAddonsByIDs(["bug526598_1@tests.mozilla.org", "bug526598_2@tests.mozilla.org"], - function([a1, a2]) { + callback_soon(function([a1, a2]) { do_check_neq(a1, null); do_check_true(a1.hasResource("install.rdf")); @@ -47,8 +47,8 @@ function run_test() { do_check_eq(newa1, null); do_check_eq(newa2, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js index 7f3719611ea4..1f70b42d559b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js @@ -31,7 +31,7 @@ function run_test() { if (!("nsIWindowsRegKey" in Components.interfaces)) do_check_true((file.permissions & 0100) == 0100); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js index 17edaf39b78d..3c1056852a32 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -332,7 +332,7 @@ function run_test() { AddonManager.getAddonsByIDs(["bug542391_2@tests.mozilla.org", "bug542391_4@tests.mozilla.org"], - function disable_and_restart([a2, a4]) { + callback_soon(function disable_and_restart([a2, a4]) { do_check_true(a2 != null && a4 != null); a2.userDisabled = true; a4.userDisabled = true; @@ -349,7 +349,7 @@ function run_test() { "bug542391_4@tests.mozilla.org", "bug542391_5@tests.mozilla.org", "bug542391_6@tests.mozilla.org"], - function(addons) { + callback_soon(function(addons) { check_state_v1(addons); WindowWatcher.expected = true; @@ -372,8 +372,8 @@ function run_test() { do_execute_soon(run_test_1); }); - }); - }); + })); + })); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index 741debb76b4c..41057cd7685f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -41,7 +41,7 @@ function end_test() { } function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -50,13 +50,13 @@ function run_test_1() { gExtensionsJSON.remove(true); do_execute_soon(check_test_1); - }); + })); } function check_test_1() { startupManager(false); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -67,5 +67,5 @@ function check_test_1() { do_check_true(gExtensionsJSON.fileSize > 0); end_test(); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js index 79ebdc66d985..2437cf7480ae 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js @@ -65,7 +65,7 @@ function run_test() { } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } // Checks switching to a different theme and back again leaves everything the @@ -196,7 +196,7 @@ function run_test_2() { function check_test_2() { restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "alternate@tests.mozilla.org"], function([d, a]) { + "alternate@tests.mozilla.org"], callback_soon(function([d, a]) { do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "alternate/1.0"); do_check_true(d.userDisabled); @@ -255,5 +255,5 @@ function check_test_2() { end_test(); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js index 36c4a2b3c021..b5ac157c7d99 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js @@ -28,7 +28,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a) { do_check_neq(a, null); do_check_eq(a.version, "1.0"); do_check_false(a.userDisabled); @@ -57,7 +57,7 @@ function run_test() { do_check_true(a.isActive); do_check_true(isExtensionInAddonsList(profileDir, a.id)); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js index facdfcc838f3..641ff87c9783 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js @@ -106,7 +106,7 @@ function run_test_2() { do_check_eq(a1, gAddon); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js index f905698eedbc..0e78630686a9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js @@ -47,7 +47,7 @@ function check_test_1() { do_check_false(b1.userDisabled); do_check_false(b1.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js index 3efb6973fe54..4869fc117377 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js @@ -141,7 +141,7 @@ function run_test() { do_check_true(a6.appDisabled); do_check_false(a6.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js index b9452b16296a..007e8270696d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js @@ -38,7 +38,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { a1.uninstall(); shutdownManager(); @@ -60,7 +60,7 @@ function run_test() { // Addon2 should have been detected do_check_neq(a2, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js index 6b5b5531e7fa..01de80634c85 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js @@ -96,12 +96,12 @@ function run_test_1() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_upgrading(a1); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_upgrading(a1); fstream.close(); @@ -114,8 +114,8 @@ function run_test_1() { a1.uninstall(); do_execute_soon(run_test_2); }); - }); - }); + })); + })); }); }); }); @@ -128,7 +128,7 @@ function run_test_2() { installAllFiles([do_get_addon("test_bug587088_1")], function() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon(a1, "1.0"); // Lock either install.rdf for unpacked add-ons or the xpi for packed add-ons. @@ -146,12 +146,12 @@ function run_test_2() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_uninstalling(a1, true); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_uninstalling(a1, true); fstream.close(); @@ -165,10 +165,10 @@ function run_test_2() { do_check_false(dir.exists()); do_check_false(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org")); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); - }); - }); + })); + })); + })); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js index dc7d8ebfa69f..7e2bf7d778fa 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js @@ -35,6 +35,6 @@ function run_test_2() { do_check_neq(a1, null); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js index 2a4da3b88b5b..3e655dc87ed9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js @@ -135,6 +135,6 @@ function run_test_3() { do_check_eq(a3, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js index 722f151f21a9..d0c9739602b0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js @@ -21,6 +21,6 @@ function run_test() { AddonManager.getAddonByID("foo", function(aAddon) { test_string_compare(); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js index 4adda9d592dd..45274b734fed 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js @@ -84,7 +84,7 @@ function run_test_1() { onUpdateFinished: function() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_false(a1.appDisabled); do_check_true(a1.isActive); @@ -115,9 +115,9 @@ function run_test_1() { do_check_false(isExtensionInAddonsList(userDir, a2.id)); do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1); - run_test_2(); + do_execute_soon(run_test_2); }); - }); + })); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); @@ -125,7 +125,7 @@ function run_test_1() { //Set up the profile function run_test_2() { - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { do_check_neq(a2, null); do_check_false(a2.appDisabled); do_check_true(a2.isActive); @@ -160,5 +160,5 @@ function run_test_2() { end_test(); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index 46b3e0393d2c..c6e8ab4e2200 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -290,7 +290,7 @@ function run_test_2() { "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org"], - function([a1, a2, a3, a4]) { + callback_soon(function([a1, a2, a3, a4]) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); do_check_true(a1.appDisabled); @@ -334,7 +334,7 @@ function run_test_2() { shutdownManager(); do_test_finished(); - }); + })); }; }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js index 617e87868ed4..579335d8aad3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js @@ -24,9 +24,7 @@ function run_test() { }, [ "onInstallStarted", "onInstallEnded", - ], function() { - do_execute_soon(check_test) - }); + ], callback_soon(check_test)); install.install(); }); } @@ -88,6 +86,6 @@ function check_test() { do_check_false(target.active); } - do_test_finished(); + do_execute_soon(do_test_finished); })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js index e85976751b98..d17e7acde6f9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js @@ -35,6 +35,6 @@ function run_test() { do_check_eq(getInstalledVersion(), "1.0"); do_check_eq(getActiveVersion(), "1.0"); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js index 6878e329fd4f..2068623390bd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js @@ -81,6 +81,6 @@ function run_test() { do_check_neq(a4, null); do_check_in_crash_annotation(addon4.id, addon4.version); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js index 3fef71a49eb6..648c7acc3ba4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js @@ -107,6 +107,6 @@ function run_test_2() { AddonManager.removeInstallListener(listener2); AddonManager.removeInstallListener(listener3); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js index 173514fe012f..c0615ef0e010 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js @@ -117,6 +117,6 @@ function run_test_4() { do_check_true(gCacheFlushed); gCacheFlushed = false; - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js b/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js index 160e605bd552..b9fc0b3abaa0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js @@ -191,6 +191,6 @@ function run_test_4() { function([a1, a2, a3, a4, a5]) { check_state(false, a1, a2, a3, a4, a5); - do_test_finished("checkcompatibility.js"); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js index ff9c3228a71a..c24b5a1b06e8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js @@ -149,7 +149,7 @@ function run_test_1() { HunspellEngine.listener = function(aEvent) { HunspellEngine.listener = null; do_check_eq(aEvent, "addDirectory"); - check_test_1(); + do_execute_soon(check_test_1); }; }); install.install(); @@ -317,7 +317,8 @@ function check_test_7() { do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_not_in_crash_annotation("ab-CD@dictionaries.addons.mozilla.org", "1.0"); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { do_check_eq(b1, null); restartManager(); @@ -327,7 +328,7 @@ function check_test_7() { do_execute_soon(run_test_8); }); - }); + })); } // Test that a bootstrapped extension dropped into the profile loads properly @@ -427,7 +428,8 @@ function run_test_16() { installAllFiles([do_get_addon("test_dictionary")], function() { // spin the event loop to let the addon finish starting do_execute_soon(function check_installed_dictionary() { - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { // Should have installed and started do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); @@ -439,7 +441,8 @@ function run_test_16() { gAppInfo.inSafeMode = true; startupManager(false); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { // Should still be stopped do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_false(b1.isActive); @@ -456,8 +459,8 @@ function run_test_16() { do_execute_soon(run_test_17); }); - }); - }); + })); + })); }); }); } @@ -483,7 +486,8 @@ function run_test_17() { startupManager(); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { // Should have installed and started do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_neq(b1, null); @@ -498,7 +502,7 @@ function run_test_17() { restartManager(); run_test_23(); - }); + })); } // Tests that installing from a URL doesn't require a restart @@ -565,7 +569,7 @@ function check_test_23() { let dir = do_get_addon_root_uri(profileDir, "ab-CD@dictionaries.addons.mozilla.org"); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 0); restartManager(); @@ -573,7 +577,7 @@ function check_test_23() { b1.uninstall(); do_execute_soon(run_test_25); }); - }); + })); }); }); } @@ -592,7 +596,8 @@ function run_test_25() { // Needs a restart to complete this so the old version stays running do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); @@ -610,7 +615,7 @@ function run_test_25() { do_execute_soon(run_test_26); }); - }); + })); }); }; @@ -624,7 +629,8 @@ function run_test_26() { // Needs a restart to complete this do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "2.0"); do_check_true(b1.isActive); @@ -644,7 +650,7 @@ function run_test_26() { b1.uninstall(); do_execute_soon(run_test_27); }); - }); + })); }); } @@ -674,7 +680,7 @@ function run_test_27() { "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], check_test_27); + ], callback_soon(check_test_27)); AddonManagerPrivate.backgroundUpdateCheck(); } @@ -720,7 +726,7 @@ function run_test_28() { "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], check_test_28); + ], callback_soon(check_test_28)); AddonManagerPrivate.backgroundUpdateCheck(); } @@ -784,7 +790,7 @@ function check_test_29(install) { ["onUninstalled", false], ] }, [ - ], finish_test_29); + ], callback_soon(finish_test_29)); b2.uninstall(); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js index 159ff7e58b62..867715863509 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js @@ -32,7 +32,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_eq(a1, null); do_check_not_in_crash_annotation(addon1.id, addon1.version); @@ -57,7 +57,7 @@ function run_test() { run_test_1(); }); - }); + })); } // Disabling an add-on should work @@ -83,7 +83,7 @@ function run_test_1() { ensure_test_completed(); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 1); do_check_eq(list[0].id, "addon1@tests.mozilla.org"); @@ -104,7 +104,7 @@ function run_test_1() { run_test_2(); }); - }); + })); }); } @@ -127,7 +127,7 @@ function run_test_2() { ensure_test_completed(); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 1); do_check_eq(list[0].id, "addon1@tests.mozilla.org"); @@ -149,7 +149,7 @@ function run_test_2() { run_test_3(); }); - }); + })); }); } @@ -161,7 +161,7 @@ function run_test_3() { ] }); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { a1.userDisabled = true; ensure_test_completed(); prepare_test({ @@ -188,7 +188,7 @@ function run_test_3() { do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE)); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js index 63089251e650..9f5bfacca884 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js @@ -149,7 +149,7 @@ function run_test_4() { // Tests that after uninstalling a restart doesn't re-install the extension function run_test_5() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { a1.uninstall(); restartManager(); @@ -159,7 +159,7 @@ function run_test_5() { do_execute_soon(run_test_6); }); - }); + })); } // Tests that upgrading the application still doesn't re-install the uninstalled @@ -257,6 +257,6 @@ function run_test_9() { a2.uninstall(); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_dss.js b/toolkit/mozapps/extensions/test/xpcshell/test_dss.js index b096d55b63b1..7b171212a032 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_dss.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dss.js @@ -115,7 +115,7 @@ function run_test() { } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } // Checks enabling one theme disables the others @@ -592,7 +592,7 @@ function run_test_9() { // Uninstalling a custom theme in use should require a restart function run_test_10() { - AddonManager.getAddonByID("theme2@tests.mozilla.org", function(oldt2) { + AddonManager.getAddonByID("theme2@tests.mozilla.org", callback_soon(function(oldt2) { prepare_test({ "theme2@tests.mozilla.org": [ ["onEnabling", false], @@ -611,7 +611,8 @@ function run_test_10() { restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([d, t2]) { + "theme2@tests.mozilla.org"], + callback_soon(function([d, t2]) { do_check_true(t2.isActive); do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); @@ -638,8 +639,8 @@ function run_test_10() { restartManager(); do_execute_soon(run_test_11); - }); - }); + })); + })); } // Installing a custom theme not in use should not require a restart @@ -665,7 +666,7 @@ function run_test_11() { }, [ "onInstallStarted", "onInstallEnded", - ], check_test_11); + ], callback_soon(check_test_11)); install.install(); }); } @@ -722,7 +723,7 @@ function check_test_12() { // Updating a custom theme in use should require a restart function run_test_13() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { prepare_test({ "theme1@tests.mozilla.org": [ ["onEnabling", false], @@ -758,10 +759,10 @@ function run_test_13() { }, [ "onInstallStarted", "onInstallEnded", - ], check_test_13); + ], callback_soon(check_test_13)); install.install(); }); - }); + })); } function check_test_13() { @@ -772,7 +773,6 @@ function check_test_13() { do_check_true(t1.isActive); do_check_false(gLWThemeChanged); t1.uninstall(); - restartManager(); do_execute_soon(run_test_14); }); @@ -781,6 +781,7 @@ function check_test_13() { // Switching from a lightweight theme to the default theme should not require // a restart function run_test_14() { + restartManager(); LightweightThemeManager.currentTheme = { id: "1", version: "1", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js b/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js index 748b8acd538a..40b532c51ae9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js @@ -164,7 +164,7 @@ function run_test_2() { function run_test_3() { restartManager(); - AddonManager.getAddonByID(gPluginIDs[0], function(p) { + AddonManager.getAddonByID(gPluginIDs[0], callback_soon(function(p) { do_check_neq(p, null); do_check_eq(p.name, "Duplicate Plugin 1"); do_check_eq(p.description, "A duplicate plugin"); @@ -178,7 +178,7 @@ function run_test_3() { do_check_eq(p.name, "Duplicate Plugin 1"); do_check_eq(p.description, "A duplicate plugin"); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_error.js b/toolkit/mozapps/extensions/test/xpcshell/test_error.js index e01dcedb4525..2184399e2bd6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_error.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_error.js @@ -85,6 +85,6 @@ function run_test_5() { do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); do_check_eq(install.error, AddonManager.ERROR_CORRUPT_FILE); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js index 545a3df4f88f..cb661e495197 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js @@ -150,7 +150,7 @@ function run_test_2() { "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], check_test_2); + ], callback_soon(check_test_2)); install.install(); }, "application/x-xpinstall"); @@ -190,7 +190,7 @@ function run_test_3() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -203,7 +203,7 @@ function run_test_3() { do_check_true(source.exists()); do_execute_soon(run_test_4); - }); + })); } // Tests that misnaming a pointer doesn't clobber the sources @@ -238,7 +238,7 @@ function run_test_5() { restartManager(); - AddonManager.getAddonByID(addon1.id, function(a1) { + AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -261,7 +261,7 @@ function run_test_5() { do_execute_soon(run_test_6); }); - }); + })); } // Removing the pointer file should uninstall the add-on @@ -273,7 +273,7 @@ function run_test_6() { restartManager(); - AddonManager.getAddonByID(addon1.id, function(a1) { + AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -288,7 +288,7 @@ function run_test_6() { do_execute_soon(run_test_7); }); - }); + })); } // Removing the pointer file and replacing it with a directory should work @@ -297,7 +297,7 @@ function run_test_7() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -315,20 +315,20 @@ function run_test_7() { a1.uninstall(); - restartManager(); - do_execute_soon(run_test_8); }); - }); + })); } // Changes to the source files should be detected function run_test_8() { + restartManager(); + writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -342,21 +342,21 @@ function run_test_8() { a1.uninstall(); - restartManager(); - do_execute_soon(run_test_9); }); - }); + })); } // Removing the add-on the pointer file points at should uninstall the add-on function run_test_9() { + restartManager(); + var dest = writeInstallRDFForExtension(addon1, sourceDir); writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID(addon1.id, function(a1) { + AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -373,7 +373,7 @@ function run_test_9() { do_execute_soon(run_test_10); }); - }); + })); } // Tests that installing a new add-on by pointer with a relative path works diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js b/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js index bdd3f7e4f96c..800933220e3d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js @@ -160,5 +160,5 @@ function onPrefChange(evt) { function onPrefChange2(evt) { do_check_eq(evt.data, testdata.dummy); - do_test_finished(); + do_execute_soon(do_test_finished); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_general.js b/toolkit/mozapps/extensions/test/xpcshell/test_general.js index 97b4971d2d89..e69b13314022 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_general.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_general.js @@ -18,7 +18,7 @@ function run_test() { AddonManager.getAddonsByTypes(null, function(list) { gCount = list.length; - run_test_1(); + do_execute_soon(run_test_1); }); } @@ -51,9 +51,8 @@ function run_test_2() { function run_test_3() { restartManager(); - AddonManager.getAddonsByTypes(null, function(addons) { + AddonManager.getAddonsByTypes(null, callback_soon(function(addons) { do_check_eq(gCount, addons.length); - shutdownManager(); do_test_finished(); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js b/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js index 33c72182dbd6..4dce15aec679 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js @@ -75,7 +75,8 @@ function run_test() { do_check_true(false); } - AddonManager.getInstallForFile(do_get_addon("test_getresource"), function(aInstall) { + AddonManager.getInstallForFile(do_get_addon("test_getresource"), + callback_soon(function(aInstall) { do_check_false(a1.hasResource("icon.png")); do_check_true(aInstall.addon.hasResource("icon.png")); @@ -84,9 +85,9 @@ function run_test() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) { do_check_eq(newa1, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }); }); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js index 2390894d35d8..598e06ed09bf 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js @@ -76,7 +76,7 @@ function run_test() { do_check_true(a5.isActive); do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js b/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js index 4e1e4f88a0a4..ab96d1fdaffc 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js @@ -50,7 +50,7 @@ function run_test_1() { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], check_test_1); + ], callback_soon(check_test_1)); // Fake a timer event gInternalManager.notify(null); @@ -105,7 +105,7 @@ function run_test_3() { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], check_test_3); + ], callback_soon(check_test_3)); // Fake a timer event gInternalManager.notify(null); @@ -189,7 +189,7 @@ function run_test_6() { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], check_test_6); + ], callback_soon(check_test_6)); // Fake a timer event gInternalManager.notify(null); @@ -261,7 +261,7 @@ function check_test_7(aInstall) { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], finish_test_7); + ], callback_soon(finish_test_7)); // Fake a timer event gInternalManager.notify(null); @@ -326,13 +326,13 @@ function check_test_8() { } function finish_test_8() { - AddonManager.getAllInstalls(function(aInstalls) { + AddonManager.getAllInstalls(callback_soon(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0].version, "2.0"); restartManager(); - AddonManager.getAddonByID("hotfix@tests.mozilla.org", function(aAddon) { + AddonManager.getAddonByID("hotfix@tests.mozilla.org", callback_soon(function(aAddon) { do_check_neq(aAddon, null); do_check_eq(aAddon.version, "2.0"); @@ -340,6 +340,6 @@ function finish_test_8() { restartManager(); end_test(); - }); - }); + })); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_install.js index 2b014b8ef158..cc85c053b9ae 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js @@ -124,7 +124,7 @@ function check_test_1(installSyncGUID) { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); - AddonManager.getAddonsWithOperationsByTypes(null, function(pendingAddons) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) { do_check_eq(pendingAddons.length, 1); do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org"); let uri = NetUtil.newURI(pendingAddons[0].iconURL); @@ -162,7 +162,7 @@ function check_test_1(installSyncGUID) { AddonManager.getAllInstalls(function(activeInstalls) { do_check_eq(activeInstalls, 0); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_neq(a1.syncGUID, null); do_check_true(a1.syncGUID.length >= 9); @@ -198,10 +198,10 @@ function check_test_1(installSyncGUID) { restartManager(); do_check_not_in_crash_annotation(a1.id, a1.version); - run_test_2(); - }); + do_execute_soon(run_test_2); + })); }); - }); + })); }); } @@ -275,7 +275,7 @@ function check_test_3(aInstall) { setExtensionModifiedTime(ext, updateDate); ensure_test_completed(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { do_check_eq(olda2, null); restartManager(); @@ -307,7 +307,7 @@ function check_test_3(aInstall) { run_test_4(); }); }); - }); + })); } // Tests that installing a new version of an existing add-on works @@ -376,7 +376,7 @@ function check_test_5(install) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); - AddonManager.getInstallsByTypes(null, function(installs) { + AddonManager.getInstallsByTypes(null, callback_soon(function(installs) { do_check_eq(installs.length, 1); do_check_eq(installs[0].addon, olda2.pendingUpgrade); restartManager(); @@ -402,17 +402,17 @@ function check_test_5(install) { do_check_true(a2.installDate <= a2.updateDate); a2.uninstall(); - restartManager(); - - run_test_6(); + do_execute_soon(run_test_6); }); }); - }); + })); }); } // Tests that an install that requires a compatibility update works function run_test_6() { + restartManager(); + prepare_test({ }, [ "onNewInstall" ]); @@ -464,7 +464,7 @@ function run_test_7() { function check_test_7() { ensure_test_completed(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(olda3) { + AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) { do_check_eq(olda3, null); restartManager(); @@ -482,15 +482,15 @@ function check_test_7() { do_check_true(isExtensionInAddonsList(profileDir, a3.id)); do_check_true(do_get_addon("test_install3").exists()); a3.uninstall(); - restartManager(); - - run_test_8(); + do_execute_soon(run_test_8); }); }); - }); + })); } function run_test_8() { + restartManager(); + AddonManager.addInstallListener(InstallListener); AddonManager.addAddonListener(AddonListener); @@ -508,7 +508,7 @@ function run_test_8() { }, [ "onInstallStarted", "onInstallEnded", - ], check_test_8); + ], callback_soon(check_test_8)); install.install(); }); } @@ -527,14 +527,14 @@ function check_test_8() { do_check_true(isExtensionInAddonsList(profileDir, a3.id)); do_check_true(do_get_addon("test_install3").exists()); a3.uninstall(); - restartManager(); - - run_test_9(); + do_execute_soon(run_test_9); }); } // Test that after cancelling a download it is removed from the active installs function run_test_9() { + restartManager(); + prepare_test({ }, [ "onNewInstall" ]); @@ -625,7 +625,7 @@ function check_test_10(install) { ensure_test_completed(); - AddonManager.getAllInstalls(function(activeInstalls) { + AddonManager.getAllInstalls(callback_soon(function(activeInstalls) { do_check_eq(activeInstalls.length, 0); restartManager(); @@ -634,9 +634,9 @@ function check_test_10(install) { AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { do_check_eq(a3, null); - run_test_11(); + do_execute_soon(run_test_11); }); - }); + })); } // Tests that a multi-package install shows up as multiple installs with the @@ -738,7 +738,7 @@ function run_test_11() { "onInstallStarted", "onInstallEnded" ] - }, check_test_11); + }, callback_soon(check_test_11)); installs[0].install(); installs[1].install(); @@ -771,14 +771,14 @@ function check_test_11() { a6.uninstall(); a7.uninstall(); - restartManager(); - - run_test_12(); + do_execute_soon(run_test_12); }); } // Same as test 11 but for a remote XPI function run_test_12() { + restartManager(); + prepare_test({ }, [ "onNewInstall", ]); @@ -829,7 +829,7 @@ function run_test_12() { "onInstallStarted", "onInstallEnded" ] - }, check_test_12); + }, callback_soon(check_test_12)); install.install(); }, "application/x-xpinstall", null, "Multi Test 4"); } @@ -896,9 +896,7 @@ function check_test_12() { a6.uninstall(); a7.uninstall(); - restartManager(); - - run_test_13(); + do_execute_soon(run_test_13); }); } @@ -906,6 +904,8 @@ function check_test_12() { // Tests that cancelling an upgrade leaves the original add-on's pendingOperations // correct function run_test_13() { + restartManager(); + installAllFiles([do_get_addon("test_install2_1")], function() { restartManager(); @@ -953,7 +953,7 @@ function check_test_13(install) { do_check_eq(install.existingAddon.id, "addon2@tests.mozilla.org"); do_check_eq(install.addon.install, install); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); do_check_eq(olda2.pendingUpgrade, install.addon); @@ -984,15 +984,15 @@ function check_test_13(install) { a2.uninstall(); - restartManager(); - - run_test_14(); + do_execute_soon(run_test_14); }); - }); + })); } // Check that cancelling the install from onDownloadStarted actually cancels it function run_test_14() { + restartManager(); + prepare_test({ }, [ "onNewInstall" ]); @@ -1347,7 +1347,7 @@ function run_test_21() { } function check_test_21(aInstall) { - AddonManager.getAllInstalls(function(aInstalls) { + AddonManager.getAllInstalls(callback_soon(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], aInstall); @@ -1370,7 +1370,7 @@ function check_test_21(aInstall) { run_test_22(); }); - }); + })); } // Tests that an install can be restarted after being cancelled diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js index f4e192f91396..70f91c56013e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js @@ -56,6 +56,6 @@ function test_4() { do_check_neq(aInstall.icons, null); do_check_eq(aInstall.icons[32], icon32_url); do_check_eq(aInstall.icons[64], icon64_url); - do_test_finished(); + do_execute_soon(do_test_finished); }, "application/x-xpinstall", null, null, { "32": icon32_url, "64": icon64_url }, null, null); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js index c5b99d5735d1..bcebc74c83c2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js @@ -121,7 +121,7 @@ function check_test_1() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); - AddonManager.getAddonsWithOperationsByTypes(null, function(pendingAddons) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) { do_check_eq(pendingAddons.length, 1); do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org"); let uri = NetUtil.newURI(pendingAddons[0].iconURL); @@ -191,7 +191,7 @@ function check_test_1() { do_execute_soon(function(){run_test_2(a1)}); }); }); - }); + })); }); } @@ -268,7 +268,7 @@ function check_test_3(aInstall) { setExtensionModifiedTime(ext, updateDate); ensure_test_completed(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { do_check_eq(olda2, null); restartManager(); @@ -299,7 +299,7 @@ function check_test_3(aInstall) { run_test_4(); }); }); - }); + })); } // Tests that installing a new version of an existing add-on works @@ -368,7 +368,7 @@ function check_test_5(install) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); - AddonManager.getInstallsByTypes(null, function(installs) { + AddonManager.getInstallsByTypes(null, callback_soon(function(installs) { do_check_eq(installs.length, 1); do_check_eq(installs[0].addon, olda2.pendingUpgrade); restartManager(); @@ -396,7 +396,7 @@ function check_test_5(install) { do_execute_soon(run_test_6); }); }); - }); + })); }); } @@ -455,7 +455,7 @@ function run_test_7() { function check_test_7() { ensure_test_completed(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(olda3) { + AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) { do_check_eq(olda3, null); restartManager(); @@ -475,7 +475,7 @@ function check_test_7() { do_execute_soon(run_test_8); }); }); - }); + })); } function run_test_8() { @@ -498,7 +498,7 @@ function run_test_8() { }, [ "onInstallStarted", "onInstallEnded", - ], check_test_8); + ], callback_soon(check_test_8)); install.install(); }); } @@ -614,7 +614,7 @@ function check_test_10(install) { ensure_test_completed(); - AddonManager.getAllInstalls(function(activeInstalls) { + AddonManager.getAllInstalls(callback_soon(function(activeInstalls) { do_check_eq(activeInstalls.length, 0); restartManager(); @@ -625,7 +625,7 @@ function check_test_10(install) { run_test_11(); }); - }); + })); } // Tests that a multi-package install shows up as multiple installs with the @@ -728,7 +728,7 @@ function run_test_11() { "onInstallStarted", "onInstallEnded" ] - }, check_test_11); + }, callback_soon(check_test_11)); installs[0].install(); installs[1].install(); @@ -820,7 +820,7 @@ function run_test_12() { "onInstallStarted", "onInstallEnded" ] - }, check_test_12); + }, callback_soon(check_test_12)); install.install(); }, "application/x-xpinstall", null, "Multi Test 4"); } @@ -944,7 +944,7 @@ function check_test_13(install) { do_check_eq(install.existingAddon.id, "addon2@tests.mozilla.org"); do_check_eq(install.addon.install, install); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); do_check_eq(olda2.pendingUpgrade, install.addon); @@ -977,7 +977,7 @@ function check_test_13(install) { do_execute_soon(run_test_14); }); - }); + })); } // Check that cancelling the install from onDownloadStarted actually cancels it @@ -1338,7 +1338,7 @@ function run_test_21() { } function check_test_21(aInstall) { - AddonManager.getAllInstalls(function(aInstalls) { + AddonManager.getAllInstalls(callback_soon(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], aInstall); @@ -1361,7 +1361,7 @@ function check_test_21(aInstall) { run_test_22(); }); - }); + })); } // Tests that an install can be restarted after being cancelled @@ -1640,5 +1640,5 @@ function finish_test_27(aInstall) { ensure_test_completed(); - do_test_finished(); + end_test(); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js b/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js index 771f452e24a7..56edb06277c6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js @@ -142,7 +142,7 @@ function run_test_2() { do_check_true(newb1.userDisabled); do_check_false(newb1.isActive); - run_test_3(); + do_execute_soon(run_test_3); }); }); } @@ -195,7 +195,7 @@ function run_test_4() { do_check_false(newb1.userDisabled); do_check_true(newb1.isActive); - run_test_5(); + do_execute_soon(run_test_5); }); }); } @@ -244,7 +244,8 @@ function check_test_7() { // check chrome reg that language pack is not registered do_check_locale_not_registered("test-langpack"); - AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", + callback_soon(function(b1) { do_check_eq(b1, null); restartManager(); @@ -252,9 +253,9 @@ function check_test_7() { AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(newb1) { do_check_eq(newb1, null); - run_test_8(); + do_execute_soon(run_test_8); }); - }); + })); } // Tests that a locale detected in the profile starts working immediately @@ -265,7 +266,8 @@ function run_test_8() { startupManager(false); - AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", + callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_false(b1.appDisabled); @@ -293,9 +295,9 @@ function run_test_8() { b2.uninstall(); ensure_test_completed(); - run_test_9(); + do_execute_soon(run_test_9); }); - }); + })); } // Tests that a locale from distribution/extensions gets installed and starts @@ -306,7 +308,7 @@ function run_test_9() { gAppInfo.version = "2.0"; startupManager(true); - AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_false(b1.appDisabled); @@ -325,5 +327,5 @@ function run_test_9() { do_check_eq(chrome.getSelectedLocale("test-langpack"), "x-testing"); do_test_finished(); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locale.js b/toolkit/mozapps/extensions/test/xpcshell/test_locale.js index 288c6b2f2699..b4c7311e5391 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locale.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locale.js @@ -34,7 +34,7 @@ function run_test_1() { }, [ "onInstallStarted", "onInstallEnded", - ], run_test_2); + ], callback_soon(run_test_2)); install.install(); }); } @@ -50,19 +50,19 @@ function run_test_2() { do_check_eq(addon.description, "fr-FR Description"); addon.userDisabled = true; - restartManager(); - - run_test_3(); + do_execute_soon(run_test_3); }); } // Test that the localized properties are still there when disabled. function run_test_3() { + restartManager(); + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "fr-FR Name"); - run_test_4(); + do_execute_soon(run_test_4); }); } @@ -82,7 +82,7 @@ function run_test_4() { do_check_eq(contributors[1], "Fr Contributor 2"); do_check_eq(contributors[2], "Fr Contributor 3"); - run_test_5(); + do_execute_soon(run_test_5); }); } @@ -97,7 +97,7 @@ function run_test_5() { do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - run_test_6(); + do_execute_soon(run_test_6); }); } @@ -106,26 +106,27 @@ function run_test_6() { Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "nl-NL"); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(addon) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "Fallback Name"); do_check_eq(addon.description, "Fallback Description"); addon.userDisabled = false; - restartManager(); - run_test_7(); - }); + do_execute_soon(run_test_7); + })); } // Test that the prefs will override the fallbacks function run_test_7() { + restartManager(); + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "Name from prefs"); - run_test_8(); + do_execute_soon(run_test_8); }); } @@ -143,6 +144,6 @@ function run_test_8() { do_check_eq(contributors[0], "Contributor 1"); do_check_eq(contributors[1], "Contributor 2"); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js old mode 100644 new mode 100755 index 91d09c07c7e2..90c86ceeddfd --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -4,8 +4,9 @@ // Checks that we rebuild something sensible from a corrupt database - Components.utils.import("resource://testing-common/httpd.js"); +Components.utils.import("resource://gre/modules/osfile.jsm"); + var testserver = new HttpServer(); testserver.start(-1); gPort = testserver.identity.primaryPort; @@ -254,188 +255,28 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // After shutting down the database won't be open so we can - // mess with permissions + // Open another handle on the JSON DB with as much Unix and Windows locking + // as we can to simulate some other process interfering with it shutdownManager(); - var savedPermissions = gExtensionsJSON.permissions; - gExtensionsJSON.permissions = 0; - - startupManager(false); - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - // Accessing the add-ons should open and recover the database - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function get_after_lock([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - // Should be correctly recovered - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - // Should be correctly recovered - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - // The compatibility update won't be recovered but it should still be - // active for this session - do_check_neq(a3, null); - do_check_true(a3.isActive); - do_check_false(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - - // The compatibility update won't be recovered and with strict - // compatibility it would not have been able to tell that it was - // previously userDisabled. However, without strict compat, it wasn't - // appDisabled, so it knows it must have been userDisabled. - do_check_neq(a4, null); - do_check_false(a4.isActive); - do_check_true(a4.userDisabled); - do_check_false(a4.appDisabled); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - - do_check_neq(a5, null); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - // Should be correctly recovered - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - // Should be correctly recovered - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - // Restarting will actually apply changes to extensions.ini which will - // then be put into the in-memory database when we next fail to load the - // real thing - restartManager(); - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - do_check_neq(a3, null); - do_check_true(a3.isActive); - do_check_false(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - - do_check_neq(a4, null); - do_check_false(a4.isActive); - do_check_true(a4.userDisabled); - do_check_false(a4.appDisabled); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - - do_check_neq(a5, null); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - // After allowing access to the original DB things should go back to as - // they were previously - shutdownManager(); - gExtensionsJSON.permissions = savedPermissions; - startupManager(); + do_print("Locking " + gExtensionsJSON.path); + let options = { + winShare: 0 + }; + if (OS.Constants.libc.O_EXLOCK) + options.unixFlags = OS.Constants.libc.O_EXLOCK; + OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then( + file => { + filePermissions = gExtensionsJSON.permissions; + if (!OS.Constants.Win) { + gExtensionsJSON.permissions = 0; + } + startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + // Accessing the add-ons should open and recover the database AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", @@ -445,7 +286,8 @@ function run_test_1() { "addon7@tests.mozilla.org", "theme1@tests.mozilla.org", "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + callback_soon(function get_after_lock([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + // Should be correctly recovered do_check_neq(a1, null); do_check_true(a1.isActive); do_check_false(a1.userDisabled); @@ -453,6 +295,7 @@ function run_test_1() { do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + // Should be correctly recovered do_check_neq(a2, null); do_check_false(a2.isActive); do_check_true(a2.userDisabled); @@ -460,6 +303,8 @@ function run_test_1() { do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + // The compatibility update won't be recovered but it should still be + // active for this session do_check_neq(a3, null); do_check_true(a3.isActive); do_check_false(a3.userDisabled); @@ -467,6 +312,10 @@ function run_test_1() { do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + // The compatibility update won't be recovered and with strict + // compatibility it would not have been able to tell that it was + // previously userDisabled. However, without strict compat, it wasn't + // appDisabled, so it knows it must have been userDisabled. do_check_neq(a4, null); do_check_false(a4.isActive); do_check_true(a4.userDisabled); @@ -493,6 +342,7 @@ function run_test_1() { do_check_false(a7.appDisabled); do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + // Should be correctly recovered do_check_neq(t1, null); do_check_false(t1.isActive); do_check_true(t1.userDisabled); @@ -500,6 +350,7 @@ function run_test_1() { do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // Should be correctly recovered do_check_neq(t2, null); do_check_true(t2.isActive); do_check_false(t2.userDisabled); @@ -507,9 +358,174 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - end_test(); + // Restarting will actually apply changes to extensions.ini which will + // then be put into the in-memory database when we next fail to load the + // real thing + restartManager(); + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + do_check_neq(a3, null); + do_check_true(a3.isActive); + do_check_false(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + + do_check_neq(a4, null); + do_check_false(a4.isActive); + do_check_true(a4.userDisabled); + do_check_false(a4.appDisabled); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + + do_check_neq(a5, null); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + // After allowing access to the original DB things should go back to as + // they were previously + shutdownManager(); + do_print("Unlocking " + gExtensionsJSON.path); + file.close(); + gExtensionsJSON.permissions = filePermissions; + startupManager(); + + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + do_check_neq(a3, null); + do_check_true(a3.isActive); + do_check_false(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + + do_check_neq(a4, null); + do_check_false(a4.isActive); + do_check_true(a4.userDisabled); + do_check_false(a4.appDisabled); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + + do_check_neq(a5, null); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + end_test(); + })); + })); })); - })); - })); + }, + do_report_unexpected_exception + ); })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 79c3439fd4e8..1f97334fd37c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -5,6 +5,8 @@ // Checks that we handle a locked database when there are extension changes // in progress +Components.utils.import("resource://gre/modules/osfile.jsm"); + // Will be left alone var addon1 = { id: "addon1@tests.mozilla.org", @@ -89,7 +91,7 @@ function run_test() { check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { a2.userDisabled = true; restartManager(); @@ -142,125 +144,152 @@ function run_test() { do_check_eq(a5.pendingOperations, AddonManager.PENDING_UPGRADE); do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - // After shutting down the database won't be open so we can lock it + // Open another handle on the JSON DB with as much Unix and Windows locking + // as we can to simulate some other process interfering with it shutdownManager(); - var savedPermissions = gExtensionsJSON.permissions; - gExtensionsJSON.permissions = 0; + do_print("Locking " + gExtensionsJSON.path); + let options = { + winShare: 0 + }; + if (OS.Constants.libc.O_EXLOCK) + options.unixFlags = OS.Constants.libc.O_EXLOCK; - startupManager(false); + OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then( + file => { + filePermissions = gExtensionsJSON.permissions; + if (!OS.Constants.Win) { + gExtensionsJSON.permissions = 0; + } + startupManager(false); - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_false(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a2.id)); + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_false(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a2.id)); - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_true(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_true(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); - do_check_eq(a4, null); + do_check_eq(a4, null); - do_check_neq(a5, null); - do_check_eq(a5.version, "2.0"); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + do_check_neq(a5, null); + do_check_eq(a5.version, "2.0"); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a6.id)); + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - // After allowing access to the original DB things should still be - // applied correctly - shutdownManager(); - gExtensionsJSON.permissions = savedPermissions; - startupManager(); + // After allowing access to the original DB things should still be + // back how they were before the lock + shutdownManager(); + file.close(); + gExtensionsJSON.permissions = filePermissions; + startupManager(); - // These things happened when we had no access to the database so - // they are seen as external changes when we get the database back :( - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, ["addon6@tests.mozilla.org"]); - check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, ["addon4@tests.mozilla.org"]); + // On Unix, we can save the DB even when the original file wasn't + // readable, so our changes were saved. On Windows, + // these things happened when we had no access to the database so + // they are seen as external changes when we get the database back + if (gXPISaveError) { + do_print("Previous XPI save failed"); + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, + ["addon6@tests.mozilla.org"]); + check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, + ["addon4@tests.mozilla.org"]); + } + else { + do_print("Previous XPI save succeeded"); + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); + } - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_false(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a2.id)); + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_false(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a2.id)); - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_true(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_true(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); - do_check_eq(a4, null); + do_check_eq(a4, null); - do_check_neq(a5, null); - do_check_eq(a5.version, "2.0"); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + do_check_neq(a5, null); + do_check_eq(a5.version, "2.0"); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a6.id)); + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - end_test(); - }); - }); + end_test(); + }); + })); + }, + do_report_unexpected_exception + ); }); }); - }); + })); } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index f38549cdaae2..79f6b89be280 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -4,8 +4,9 @@ // Checks that we rebuild something sensible from a corrupt database - Components.utils.import("resource://testing-common/httpd.js"); +Components.utils.import("resource://gre/modules/osfile.jsm"); + var testserver = new HttpServer(); testserver.start(-1); gPort = testserver.identity.primaryPort; @@ -254,184 +255,28 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // After shutting down the database won't be open so we can lock it + // Open another handle on the JSON DB with as much Unix and Windows locking + // as we can to simulate some other process interfering with it shutdownManager(); - var savedPermissions = gExtensionsJSON.permissions; - gExtensionsJSON.permissions = 0; + do_print("Locking " + gExtensionsJSON.path); + let options = { + winShare: 0 + }; + if (OS.Constants.libc.O_EXLOCK) + options.unixFlags = OS.Constants.libc.O_EXLOCK; - startupManager(false); - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - // Accessing the add-ons should open and recover the database - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - // Should be correctly recovered - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - // Should be correctly recovered - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - // The compatibility update won't be recovered but it should still be - // active for this session - do_check_neq(a3, null); - do_check_true(a3.isActive); - do_check_false(a3.userDisabled); - do_check_true(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE); - do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - - // The compatibility update won't be recovered and it will not have been - // able to tell that it was previously userDisabled - do_check_neq(a4, null); - do_check_false(a4.isActive); - do_check_false(a4.userDisabled); - do_check_true(a4.appDisabled); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - - do_check_neq(a5, null); - do_check_false(a5.isActive); - do_check_false(a5.userDisabled); - do_check_true(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - // Should be correctly recovered - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - // Should be correctly recovered - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - // Restarting will actually apply changes to extensions.ini which will - // then be put into the in-memory database when we next fail to load the - // real thing - restartManager(); - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.userDisabled); - do_check_true(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a3.id)); - - do_check_neq(a4, null); - do_check_false(a4.isActive); - do_check_false(a4.userDisabled); - do_check_true(a4.appDisabled); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - - do_check_neq(a5, null); - do_check_false(a5.isActive); - do_check_false(a5.userDisabled); - do_check_true(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - // After allowing access to the original DB things should go back to as - // they were previously - shutdownManager(); - gExtensionsJSON.permissions = savedPermissions; + OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then( + file => { + filePermissions = gExtensionsJSON.permissions; + if (!OS.Constants.Win) { + gExtensionsJSON.permissions = 0; + } startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + // Accessing the add-ons should open and recover the database AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", @@ -442,6 +287,7 @@ function run_test_1() { "theme1@tests.mozilla.org", "theme2@tests.mozilla.org"], callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + // Should be correctly recovered do_check_neq(a1, null); do_check_true(a1.isActive); do_check_false(a1.userDisabled); @@ -449,6 +295,7 @@ function run_test_1() { do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + // Should be correctly recovered do_check_neq(a2, null); do_check_false(a2.isActive); do_check_true(a2.userDisabled); @@ -456,17 +303,21 @@ function run_test_1() { do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + // The compatibility update won't be recovered but it should still be + // active for this session do_check_neq(a3, null); do_check_true(a3.isActive); do_check_false(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE); do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + // The compatibility update won't be recovered and it will not have been + // able to tell that it was previously userDisabled do_check_neq(a4, null); do_check_false(a4.isActive); - do_check_true(a4.userDisabled); - do_check_false(a4.appDisabled); + do_check_false(a4.userDisabled); + do_check_true(a4.appDisabled); do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isExtensionInAddonsList(profileDir, a4.id)); @@ -489,6 +340,7 @@ function run_test_1() { do_check_false(a7.appDisabled); do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + // Should be correctly recovered do_check_neq(t1, null); do_check_false(t1.isActive); do_check_true(t1.userDisabled); @@ -496,6 +348,7 @@ function run_test_1() { do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // Should be correctly recovered do_check_neq(t2, null); do_check_true(t2.isActive); do_check_false(t2.userDisabled); @@ -503,9 +356,195 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - end_test(); + // Restarting will actually apply changes to extensions.ini which will + // then be put into the in-memory database when we next fail to load the + // real thing + restartManager(); + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.userDisabled); + do_check_true(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + + do_check_neq(a4, null); + do_check_false(a4.isActive); + do_check_false(a4.userDisabled); + do_check_true(a4.appDisabled); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + + do_check_neq(a5, null); + do_check_false(a5.isActive); + do_check_false(a5.userDisabled); + do_check_true(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + // After allowing access to the original DB things should go back to as + // back how they were before the lock + shutdownManager(); + do_print("Unlocking " + gExtensionsJSON.path); + file.close(); + gExtensionsJSON.permissions = filePermissions; + startupManager(false); + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + do_check_neq(a3, null); + do_check_false(a3.userDisabled); + // On Unix, we may be able to save our changes over the locked DB so we + // remember that this extension was changed to disabled. On Windows we + // couldn't replace the old DB so we read the older version of the DB + // where the extension is enabled + if (gXPISaveError) { + do_print("XPI save failed"); + do_check_true(a3.isActive); + do_check_false(a3.appDisabled); + do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + } + else { + do_print("XPI save succeeded"); + do_check_false(a3.isActive); + do_check_true(a3.appDisabled); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + } + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a4, null); + do_check_false(a4.isActive); + // The reverse of the platform difference for a3 - Unix should + // stay the same as the last iteration because the save succeeded, + // Windows should still say userDisabled + if (OS.Constants.Win) { + do_check_true(a4.userDisabled); + do_check_false(a4.appDisabled); + } + else { + do_check_false(a4.userDisabled); + do_check_true(a4.appDisabled); + } + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a5, null); + do_check_false(a5.isActive); + do_check_false(a5.userDisabled); + do_check_true(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + end_test(); + })); + })); })); - })); - })); + }, + do_report_unexpected_exception + ); })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js b/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js index e228dc61305f..061a3a6b2b85 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js @@ -557,6 +557,6 @@ function run_test() { do_check_eq(a26.optionsType, AddonManager.OPTIONS_TYPE_INLINE_INFO); do_check_neq(a26.optionsURL, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js b/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js index a01701c57bc3..6a116746549b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js @@ -140,7 +140,7 @@ function run_test_2(uri) { do_check_true(newb1.userDisabled); check_mapping(uri, newb1.id); - run_test_3(uri); + do_execute_soon(() => run_test_3(uri)); }); }); } @@ -174,7 +174,7 @@ function run_test_4() { let uri = newb1.getResourceURI("."); check_mapping(uri, newb1.id); - run_test_5(); + do_execute_soon(run_test_5); }); }); } @@ -187,7 +187,7 @@ function run_test_5() { let uri = b1.getResourceURI("."); check_mapping(uri, b1.id); - run_test_invalidarg(); + do_execute_soon(run_test_invalidarg); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js index 478e697cb78f..ffef6b7d169b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js @@ -246,6 +246,6 @@ function run_test() { do_check_false(stagedXPIs.exists()); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js index c703b25c19a9..c213bace73fa 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -254,6 +254,6 @@ function run_test() { do_check_false(a8.strictCompatibility); do_check_true(a8.foreignInstall); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js index ef7bdd3f603c..f27d53c3fc9f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js @@ -234,6 +234,6 @@ function run_test() { do_check_false(stagedXPIs.exists()); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js index 91a05fa358e3..0109dcf927a6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js @@ -134,6 +134,6 @@ function run_test() { do_check_false(d.appDisabled); do_check_false(d.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js b/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js index d191ed33bdde..f9b7da073cd6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js @@ -59,7 +59,7 @@ function run_test_2() { "addon1@tests.mozilla.org": [ ["onPropertyChanged", ["appDisabled"]] ] - }, [], do_test_finished); + }, [], callback_soon(do_test_finished)); AddonManager.strictCompatibility = false; }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js index 917e2aa7d4d3..ba24e92e019c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js @@ -75,7 +75,7 @@ function run_test() { } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } function sortAddons(addons) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js b/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js index 3de712cdae40..7051ca14470b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js @@ -173,6 +173,6 @@ function run_test_4() { Services.prefs.clearUserPref("plugins.click_to_play"); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_registry.js b/toolkit/mozapps/extensions/test/xpcshell/test_registry.js index a2d8b80a31d0..010250457986 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_registry.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_registry.js @@ -146,6 +146,6 @@ function run_test_4() { do_check_eq(a1, null); do_check_neq(a2, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js b/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js index dbbe848fc5db..05647f807f3b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js @@ -33,7 +33,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_eq(a1, null); do_check_not_in_crash_annotation(addon1.id, addon1.version); @@ -57,7 +57,7 @@ function run_test() { run_test_1(); }); - }); + })); } // Disabling an add-on should work @@ -110,6 +110,6 @@ function run_test_2() { ensure_test_completed(); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index a14f07e43471..36e3da0465a3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -828,7 +828,7 @@ function run_test_12() { "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org"], - function([a1, a2, a3, a4, a5]) { + callback_soon(function([a1, a2, a3, a4, a5]) { do_check_neq(a1, null); do_check_false(a1.userDisabled); do_check_true(a1.isActive); @@ -917,8 +917,8 @@ function run_test_12() { do_check_true(a3.userDisabled); do_check_false(a3.isActive); - end_test(); + do_execute_soon(end_test); }); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js index c73a412c4870..f57fa4d22d3b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js @@ -86,7 +86,7 @@ add_test(function test_error_on_duplicate_syncguid_insert() { do_execute_soon(function duplicate_syncguid_install_ended() { restartManager(); - AddonManager.getAddonsByIDs(installIDs, function(addons) { + AddonManager.getAddonsByIDs(installIDs, callback_soon(function(addons) { let initialGUID = addons[1].syncGUID; try { @@ -102,7 +102,7 @@ add_test(function test_error_on_duplicate_syncguid_insert() { run_next_test(); }); } - }); + })); }); } } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js b/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js index 011288e814a6..ef4f2aee562e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js @@ -141,6 +141,6 @@ function run_test() { do_check_false(isExtensionInAddonsList(profileDir, a5.id)); do_check_not_in_crash_annotation(addon5.id, addon5.version); - do_test_finished(); + do_execute_soon(do_test_finished); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js index 6b2e0c94da3c..f201c776d879 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js @@ -126,7 +126,7 @@ function run_test() { } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } // Checks enabling one theme disables the others @@ -613,7 +613,7 @@ function run_test_9() { // Uninstalling a custom theme in use should require a restart function run_test_10() { - AddonManager.getAddonByID("theme2@tests.mozilla.org", function(oldt2) { + AddonManager.getAddonByID("theme2@tests.mozilla.org", callback_soon(function(oldt2) { prepare_test({ "theme2@tests.mozilla.org": [ "onEnabling", @@ -654,7 +654,7 @@ function run_test_10() { do_execute_soon(run_test_11); }); - }); + })); } // Installing a custom theme not in use should not require a restart @@ -742,7 +742,7 @@ function check_test_12() { // Updating a custom theme in use should require a restart function run_test_13() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { prepare_test({ "theme1@tests.mozilla.org": [ "onEnabling", @@ -777,16 +777,16 @@ function run_test_13() { }, [ "onInstallStarted", "onInstallEnded", - ], check_test_13); + ], callback_soon(check_test_13)); install.install(); }); - }); + })); } function check_test_13() { restartManager(); - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { do_check_neq(t1, null); do_check_true(t1.isActive); do_check_false(gLWThemeChanged); @@ -794,7 +794,7 @@ function check_test_13() { restartManager(); do_execute_soon(run_test_14); - }); + })); } // Switching from a lightweight theme to the default theme should not require @@ -846,14 +846,15 @@ function run_test_15() { restartManager(); installAllFiles([do_get_addon("test_theme")], function() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { t1.userDisabled = false; restartManager(); do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1/1.0"); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], function([d, t1]) { + "theme1@tests.mozilla.org"], + callback_soon(function([d, t1]) { do_check_true(d.userDisabled); do_check_false(d.appDisabled); do_check_false(d.isActive); @@ -877,8 +878,8 @@ function run_test_15() { do_execute_soon(run_test_16); }); - }); - }); + })); + })); }); } @@ -932,13 +933,14 @@ function run_test_17() { function run_test_18() { restartManager(2); - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { t1.userDisabled = false; restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], function([d, t1]) { + "theme1@tests.mozilla.org"], + callback_soon(function([d, t1]) { do_check_true(d.userDisabled); do_check_false(d.appDisabled); do_check_false(d.isActive); @@ -980,8 +982,8 @@ function run_test_18() { do_execute_soon(run_test_19); }); - }); - }); + })); + })); } // Disabling the active persona should switch back to the default theme @@ -1044,7 +1046,7 @@ function run_test_20() { // Tests that cached copies of a lightweight theme have the right permissions // and pendingOperations during the onEnabling event function run_test_21() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { // Switch to a custom theme so we can test pendingOperations properly. prepare_test({ @@ -1086,5 +1088,5 @@ function run_test_21() { end_test(); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js index 38349a22f542..02d37d89b745 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js @@ -25,7 +25,7 @@ function run_test() { do_test_pending(); startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { do_check_eq(olda1, null); writeInstallRDFForExtension(addon1, profileDir); @@ -40,13 +40,13 @@ function run_test() { do_check_eq(a1.pendingOperations, 0); do_check_in_crash_annotation(addon1.id, addon1.version); - run_test_1(); + do_execute_soon(run_test_1); }); - }); + })); } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } // Uninstalling an add-on should work. diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index e222b8e126e2..793a8d545521 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -210,7 +210,7 @@ function run_test_2(install) { function check_test_2() { ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { do_check_neq(olda1, null); do_check_eq(olda1.version, "1.0"); do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); @@ -234,7 +234,7 @@ function check_test_2() { a1.uninstall(); do_execute_soon(run_test_3); }); - }); + })); } @@ -401,7 +401,7 @@ function continue_test_6(install) { }, [ "onInstallStarted", "onInstallEnded", - ], check_test_6); + ], callback_soon(check_test_6)); } function check_test_6(install) { @@ -413,14 +413,14 @@ function check_test_6(install) { do_check_eq(a1.version, "2.0"); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); a1.uninstall(); - restartManager(); - - run_test_7(); + do_execute_soon(run_test_7); }); } // Test that background update checks work for lightweight themes function run_test_7() { + restartManager(); + LightweightThemeManager.currentTheme = { id: "1", version: "1", @@ -578,7 +578,7 @@ function check_test_7_cache() { do_check_eq(p1.installDate.getTime(), gInstallDate); do_check_true(p1.installDate.getTime() < p1.updateDate.getTime()); - run_test_8(); + do_execute_soon(run_test_8); }); } @@ -662,7 +662,7 @@ function run_test_8() { restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { a2.userDisabled = true; restartManager(); @@ -756,7 +756,7 @@ function run_test_8() { let compatListener = { onUpdateFinished: function(addon, error) { if (--count == 0) - run_next_test(); + do_execute_soon(run_next_test); } }; @@ -778,7 +778,7 @@ function run_test_8() { a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); - }); + })); } // Tests that if an install.rdf claims compatibility then the add-on will be @@ -828,7 +828,7 @@ function run_test_11() { onUpdateFinished: function(addon) { do_check_true(addon.isCompatible); - run_test_12(); + do_execute_soon(run_test_12); } }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); @@ -1296,7 +1296,7 @@ function continue_test_20(install) { }, [ "onInstallStarted", "onInstallEnded", - ], check_test_20); + ], callback_soon(check_test_20)); } function check_test_20(install) { @@ -1308,8 +1308,10 @@ function check_test_20(install) { do_check_eq(a12.version, "2.0"); do_check_eq(a12.type, "extension"); a12.uninstall(); - restartManager(); - end_test(); + do_execute_soon(() => { + restartManager(); + end_test(); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 993f91f732a7..1eba7779aa71 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -207,7 +207,7 @@ function run_test_2(install) { function check_test_2() { ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { do_check_neq(olda1, null); do_check_eq(olda1.version, "1.0"); do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); @@ -228,7 +228,7 @@ function check_test_2() { a1.uninstall(); do_execute_soon(run_test_3); }); - }); + })); } @@ -496,7 +496,7 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - run_test_8(); + do_execute_soon(run_test_8); }); } @@ -580,7 +580,7 @@ function run_test_8() { restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { a2.userDisabled = true; restartManager(); @@ -696,7 +696,7 @@ function run_test_8() { a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); - }); + })); } // Tests that if an install.rdf claims compatibility then the add-on will be @@ -746,7 +746,7 @@ function run_test_11() { onUpdateFinished: function(addon) { do_check_false(addon.isCompatible); - run_test_12(); + do_execute_soon(run_test_12); } }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); @@ -1085,7 +1085,7 @@ function run_test_17() { }, onUpdateFinished: function() { - end_test(); + do_execute_soon(end_test); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js b/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js index 069ed0671141..e8aea0301697 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js @@ -93,7 +93,7 @@ function run_test_1() { } function check_test_1(install) { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { // Existing add-on should have a pending upgrade do_check_neq(a1.pendingUpgrade, null); do_check_eq(a1.pendingUpgrade.id, "addon2@tests.mozilla.org"); @@ -118,7 +118,7 @@ function check_test_1(install) { do_execute_soon(run_test_2); }); - }); + })); } // Test that when the new add-on already exists we just upgrade that @@ -164,7 +164,8 @@ function run_test_2() { function check_test_2(install) { AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org"], function([a1, a2]) { + "addon2@tests.mozilla.org"], + callback_soon(function([a1, a2]) { do_check_eq(a1.pendingUpgrade, null); // Existing add-on should have a pending upgrade do_check_neq(a2.pendingUpgrade, null); @@ -184,7 +185,7 @@ function check_test_2(install) { do_execute_soon(run_test_3); }); - }); + })); } // Test that we rollback correctly when removing the old add-on fails @@ -225,7 +226,7 @@ function run_test_3() { } function check_test_3(install) { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { // Existing add-on should have a pending upgrade do_check_neq(a1.pendingUpgrade, null); do_check_eq(a1.pendingUpgrade.id, "addon2@tests.mozilla.org"); @@ -248,7 +249,8 @@ function check_test_3(install) { fstream.close(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org"], function([a1, a2]) { + "addon2@tests.mozilla.org"], + callback_soon(function([a1, a2]) { // Should not have installed the new add-on but it should still be // pending install do_check_neq(a1, null); @@ -266,8 +268,8 @@ function check_test_3(install) { do_execute_soon(run_test_4); }); - }); - }); + })); + })); } // Tests that upgrading to a bootstrapped add-on works but requires a restart @@ -306,7 +308,8 @@ function run_test_4() { function check_test_4() { AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", - "addon3@tests.mozilla.org"], function([a2, a3]) { + "addon3@tests.mozilla.org"], + callback_soon(function([a2, a3]) { // Should still be pending install even though the new add-on is restartless do_check_neq(a2, null); do_check_eq(a3, null); @@ -330,7 +333,7 @@ function check_test_4() { do_execute_soon(run_test_5); }); - }); + })); } // Tests that upgrading to another bootstrapped add-on works without a restart @@ -349,7 +352,8 @@ function run_test_5() { function check_test_5() { AddonManager.getAddonsByIDs(["addon3@tests.mozilla.org", - "addon4@tests.mozilla.org"], function([a3, a4]) { + "addon4@tests.mozilla.org"], + callback_soon(function([a3, a4]) { // Should have updated do_check_eq(a3, null); do_check_neq(a4, null); @@ -370,7 +374,7 @@ function check_test_5() { run_test_6(); }); - }); + })); } // Tests that upgrading to a non-bootstrapped add-on works but requires a restart @@ -389,7 +393,8 @@ function run_test_6() { function check_test_6() { AddonManager.getAddonsByIDs(["addon4@tests.mozilla.org", - "addon2@tests.mozilla.org"], function([a4, a2]) { + "addon2@tests.mozilla.org"], + callback_soon(function([a4, a2]) { // Should still be pending install even though the old add-on is restartless do_check_neq(a4, null); do_check_eq(a2, null); @@ -413,5 +418,5 @@ function check_test_6() { end_test(); }); - }); + })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js index a5e2c532bed6..67bec392efd8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js @@ -93,7 +93,7 @@ function end_test() { globalDir.append(do_get_expected_addon_name("addon4@tests.mozilla.org")); globalDir.remove(true); } - do_test_finished(); + do_execute_soon(do_test_finished); } // Test that the test extensions are all installed diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js index 22aa7114fd1b..96e1cbd8d19b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js @@ -96,7 +96,7 @@ function end_test() { Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); - do_test_finished(); + do_execute_soon(do_test_finished); } // Test that the test extensions are all installed From 6970ddc3f1cc691137d818feee52fe9e64f8d104 Mon Sep 17 00:00:00 2001 From: Wes Johnston Date: Tue, 6 Aug 2013 22:13:21 -0700 Subject: [PATCH 17/40] Bug 818987 - Links to media files should have an 'Open in app' option. r=wesj --- mobile/android/base/GeckoApp.java | 19 ++++++ mobile/android/chrome/content/HelperApps.js | 71 ++++++++++++++++----- mobile/android/chrome/content/browser.js | 20 +++++- 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 207d0c8df6a9..dad91700a854 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -721,6 +721,21 @@ abstract public class GeckoApp // something went wrong. Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number"); } + } else if (event.equals("Intent:GetHandlers")) { + Intent intent = GeckoAppShell.getOpenURIIntent(sAppContext, message.optString("url"), + message.optString("mime"), message.optString("action"), message.optString("title")); + String[] handlers = GeckoAppShell.getHandlersForIntent(intent); + ArrayList appList = new ArrayList(handlers.length); + for (int i = 0; i < handlers.length; i++) { + appList.add(handlers[i]); + } + JSONObject handlersJSON = new JSONObject(); + handlersJSON.put("apps", new JSONArray(appList)); + mCurrentResponse = handlersJSON.toString(); + } else if (event.equals("Intent:Open")) { + GeckoAppShell.openUriExternal(message.optString("url"), + message.optString("mime"), message.optString("packageName"), + message.optString("className"), message.optString("action"), message.optString("title")); } } catch (Exception e) { Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); @@ -1507,6 +1522,8 @@ abstract public class GeckoApp registerEventListener("Update:Install"); registerEventListener("PrivateBrowsing:Data"); registerEventListener("Contact:Add"); + registerEventListener("Intent:Open"); + registerEventListener("Intent:GetHandlers"); if (SmsManager.getInstance() != null) { SmsManager.getInstance().start(); @@ -2061,6 +2078,8 @@ abstract public class GeckoApp unregisterEventListener("Update:Install"); unregisterEventListener("PrivateBrowsing:Data"); unregisterEventListener("Contact:Add"); + unregisterEventListener("Intent:Open"); + unregisterEventListener("Intent:GetHandlers"); deleteTempFiles(); diff --git a/mobile/android/chrome/content/HelperApps.js b/mobile/android/chrome/content/HelperApps.js index a7ea014ded7b..b2488630b02b 100644 --- a/mobile/android/chrome/content/HelperApps.js +++ b/mobile/android/chrome/content/HelperApps.js @@ -3,6 +3,19 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { + let ContentAreaUtils = {}; + Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils); + return ContentAreaUtils; +}); + +function getBridge() { + return Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge); +} + +function sendMessageToJava(aMessage) { + return getBridge().handleGeckoMessage(JSON.stringify(aMessage)); +} var HelperApps = { get defaultHttpHandlers() { @@ -34,28 +47,54 @@ var HelperApps = { let handlerInfoProto = this.protoSvc.getProtocolHandlerInfoFromOS(uri, {}); return handlerInfoProto.possibleApplicationHandlers; }, - + getAppsForUri: function getAppsFor(uri) { let found = []; - let handlerInfoProto = this.urlHandlerService.getURLHandlerInfoFromOS(uri, {}); - let urlHandlers = handlerInfoProto.possibleApplicationHandlers; - for (var i = 0; i < urlHandlers.length; i++) { - let urlApp = urlHandlers.queryElementAt(i, Ci.nsIHandlerApp); - if (!this.defaultHttpHandlers[urlApp.name]) { - found.push(urlApp); - } + let mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || ""; + // empty action string defaults to android.intent.action.VIEW + let msg = { + type: "Intent:GetHandlers", + mime: mimeType, + action: "", + url: uri.spec, + packageName: "", + className: "" + }; + let apps = this._parseApps(JSON.parse(sendMessageToJava(msg))); + for (let i = 0; i < apps.length; i++) { + let appName = apps[i].name; + if (appName.length > 0 && !this.defaultHttpHandlers[appName]) + found.push(apps[i]); } return found; }, - + openUriInApp: function openUriInApp(uri) { - var possibleHandlers = this.getAppsForUri(uri); - if (possibleHandlers.length == 1) { - possibleHandlers[0].launchWithURI(uri); - } else if (possibleHandlers.length > 0) { - let handlerInfoProto = this.urlHandlerService.getURLHandlerInfoFromOS(uri, {}); - handlerInfoProto.preferredApplicationHandler.launchWithURI(uri); + let mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || ""; + let msg = { + type: "Intent:Open", + mime: mimeType, + action: "", + url: uri.spec, + packageName: "", + className: "" + }; + sendMessageToJava(msg); + }, + + _parseApps: function _parseApps(aJSON) { + // aJSON -> {apps: [app1Label, app1Default, app1PackageName, app1ActivityName, app2Label, app2Defaut, ...]} + // see GeckoAppShell.java getHandlersForIntent function for details + let appInfo = aJSON.apps; + const numAttr = 4; // 4 elements per ResolveInfo: label, default, package name, activity name. + let apps = []; + for (let i = 0; i < appInfo.length; i += numAttr) { + apps.push({"name" : appInfo[i], + "isDefault" : appInfo[i+1], + "packageName" : appInfo[i+2], + "activityName" : appInfo[i+3]}); } + return apps; }, showDoorhanger: function showDoorhanger(aUri, aCallback) { @@ -80,7 +119,7 @@ var HelperApps = { message = strings.formatStringFromName("helperapps.openWithApp2", [apps[0].name], 1); else message = strings.GetStringFromName("helperapps.openWithList2"); - + let buttons = [{ label: strings.GetStringFromName("helperapps.open"), callback: function(aChecked) { diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 725b1b55a433..4c3cc905f753 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -7631,12 +7631,26 @@ let Reader = { var ExternalApps = { _contextMenuId: -1, + // extend _getLink to pickup html5 media links. + _getMediaLink: function(aElement) { + let uri = NativeWindow.contextmenus._getLink(aElement); + if (uri == null) { + if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && (aElement instanceof Ci.nsIDOMHTMLMediaElement && mediaSrc)) { + try { + let mediaSrc = aElement.currentSrc || aElement.src; + uri = ContentAreaUtils.makeURI(mediaSrc, null, null); + } catch (e) {} + } + } + return uri; + }, + init: function helper_init() { this._contextMenuId = NativeWindow.contextmenus.add(function(aElement) { let uri = null; var node = aElement; while (node && !uri) { - uri = NativeWindow.contextmenus._getLink(node); + uri = ExternalApps._getMediaLink(node); node = node.parentNode; } let apps = []; @@ -7654,7 +7668,7 @@ var ExternalApps = { filter: { matches: function(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); + let uri = ExternalApps._getMediaLink(aElement); let apps = []; if (uri) { apps = HelperApps.getAppsForUri(uri); @@ -7664,7 +7678,7 @@ var ExternalApps = { }, openExternal: function(aElement) { - let uri = NativeWindow.contextmenus._getLink(aElement); + let uri = ExternalApps._getMediaLink(aElement); HelperApps.openUriInApp(uri); } }; From 4d1f19f1753c02a7309a9ee13256e8f7362cfd11 Mon Sep 17 00:00:00 2001 From: Sam Foster Date: Thu, 8 Aug 2013 17:02:19 -0700 Subject: [PATCH 18/40] Bug 902246 - Utility methods for adding rgb(a) colors together. r=mbrubeck --- browser/metro/modules/colorUtils.jsm | 62 +++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/browser/metro/modules/colorUtils.jsm b/browser/metro/modules/colorUtils.jsm index faa7b953921c..cc887295a995 100644 --- a/browser/metro/modules/colorUtils.jsm +++ b/browser/metro/modules/colorUtils.jsm @@ -50,13 +50,65 @@ let ColorUtils = { return textColor; }, + toCSSRgbColor: function toCSSRgbColor(r, g, b, a) { + var values = [Math.round(r), Math.round(g), Math.round(b)]; + if(undefined !== a && a < 1) { + values.push(a); + return 'rgba('+values.join(',')+')'; + } + return 'rgb('+values.join(',')+')'; + }, + /** - * converts a decimal(base10) number into rgb string + * converts a decimal(base10) number into CSS rgb color value string */ convertDecimalToRgbColor: function convertDecimalToRgbColor(aColor) { - let r = (aColor & 0xff0000) >> 16; - let g = (aColor & 0x00ff00) >> 8; - let b = (aColor & 0x0000ff); - return "rgb("+r+","+g+","+b+")"; + let [r,g,b,a] = this.unpackDecimalColorWord(aColor); + return this.toCSSRgbColor(r,g,b,a); + }, + + /** + * unpack a decimal(base10) word for r,g,b,a values + */ + unpackDecimalColorWord: function unpackDecimalColorWord(aColor) { + let a = (aColor & 0xff000000) >> 24; + let r = (aColor & 0x00ff0000) >> 16; + let g = (aColor & 0x0000ff00) >> 8; + let b = (aColor & 0x000000ff); + // NB: falsy alpha treated as undefined, fully opaque + return a ? [r,g,b,a/255] : [r,g,b]; + }, + + /** + * create a decimal(base10) word for r,g,b values + */ + createDecimalColorWord: function createDecimalColorWord(r, g, b, a) { + let rgb = 0; + rgb |= b; + rgb |= (g << 8); + rgb |= (r << 16); + // pack alpha value if one is given + if(undefined !== a && a < 1) + rgb |= (Math.round(a*255) << 24); + return rgb; + }, + + /** + * Add 2 rgb(a) colors to get a flat color + */ + addRgbColors: function addRgbColors(color1, color2) { + let [r1, g1, b1] = this.unpackDecimalColorWord(color1); + let [r2, g2, b2, alpha] = this.unpackDecimalColorWord(color2); + + let color = {}; + // early return if 2nd color is opaque + if (!alpha || alpha >= 1) + return color2; + + return this.createDecimalColorWord( + Math.min(255, alpha * r2 + (1 - alpha) * r1), + Math.min(255, alpha * g2 + (1 - alpha) * g1), + Math.min(255, alpha * b2 + (1 - alpha) * b1) + ); } }; From a17df00e29f5c4daab8859c43a212c55d6c4b629 Mon Sep 17 00:00:00 2001 From: Jonathan Wilde Date: Wed, 17 Jul 2013 21:50:51 -0700 Subject: [PATCH 19/40] Bug 856264 - Part 1 - Failing tests for context menu. r=jimm --HG-- extra : rebase_source : 06a8e9b6f39768e175224cf1e40889f10d979748 --- .../metro/base/tests/mochitest/Makefile.in | 3 +- .../mochitest/browser_context_menu_tests.js | 113 ++++++++++++++---- .../browser_context_menu_tests_04.html | 15 +++ .../mochitest/browser_form_auto_complete.js | 4 +- .../browser_selection_frame_content.js | 2 +- .../mochitest/browser_selection_urlbar.js | 2 +- browser/metro/base/tests/mochitest/head.js | 22 +++- 7 files changed, 131 insertions(+), 30 deletions(-) create mode 100644 browser/metro/base/tests/mochitest/browser_context_menu_tests_04.html diff --git a/browser/metro/base/tests/mochitest/Makefile.in b/browser/metro/base/tests/mochitest/Makefile.in index 4add0ea6a61d..998bcc29239a 100644 --- a/browser/metro/base/tests/mochitest/Makefile.in +++ b/browser/metro/base/tests/mochitest/Makefile.in @@ -10,12 +10,13 @@ relativesrcdir = @relativesrcdir@ include $(DEPTH)/config/autoconf.mk -# Disabled for intermittent failures +# Disabled for intermittent failures # Bug 880739 # browser_context_menu_tests.js \ # browser_context_menu_tests_01.html \ # browser_context_menu_tests_02.html \ # browser_context_menu_tests_03.html \ +# browser_context_menu_tests_04.html \ MOCHITEST_METRO_FILES = \ head.js \ diff --git a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js index c8cdfe9b8b5c..11b0dfbc8e06 100644 --- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js +++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js @@ -58,7 +58,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); // selected text context: checkContextUIMenuItemVisibility(["context-copy", @@ -91,7 +91,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); // selected text context: checkContextUIMenuItemVisibility(["context-copy", @@ -113,7 +113,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); // selected text context: checkContextUIMenuItemVisibility(["context-open-in-new-tab", @@ -135,7 +135,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextUIMenuItemVisibility(["context-select", "context-select-all"]); @@ -159,7 +159,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextUIMenuItemVisibility(["context-cut", "context-copy"]); @@ -191,7 +191,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); // selected text context: checkContextUIMenuItemVisibility(["context-cut", @@ -212,7 +212,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); // selected text context: checkContextUIMenuItemVisibility(["context-cut", @@ -236,7 +236,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextUIMenuItemVisibility(["context-cut", "context-copy"]); @@ -272,7 +272,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); // selected text context: checkContextUIMenuItemVisibility(["context-paste"]); @@ -295,7 +295,7 @@ gTests.push({ yield promise; // should *not* be visible - ok(!ContextMenuUI._menuPopup._visible, "is visible"); + ok(!ContextMenuUI._menuPopup.visible, "is visible"); // the test above will invoke the app bar yield hideContextUI(); @@ -336,7 +336,7 @@ gTests.push({ yield promise; // should be visible and at a specific position - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); let notificationBox = Browser.getNotificationBox(); let notification = notificationBox.getNotificationWithValue("popup-blocked"); @@ -411,7 +411,7 @@ gTests.push({ // Context menu options /* XXX disabled temporarily due to bug 880739 - + // image01 - 1x1x100x100 let promise = waitForEvent(document, "popupshown"); sendContextMenuClickToWindow(win, 10, 10); @@ -419,7 +419,7 @@ gTests.push({ purgeEventQueue(); - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextUIMenuItemVisibility(["context-save-image-lib", "context-copy-image", @@ -465,7 +465,7 @@ gTests.push({ let promise = waitForEvent(document, "popupshown"); sendContextMenuClickToWindow(win, 20, 20); yield promise; - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); let menuItem = document.getElementById("context-copy-image"); ok(menuItem, "menu item exists"); @@ -486,7 +486,7 @@ gTests.push({ promise = waitForEvent(document, "popupshown"); sendContextMenuClickToWindow(win, 30, 30); yield promise; - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); menuItem = document.getElementById("context-copy-image-loc"); ok(menuItem, "menu item exists"); @@ -519,7 +519,7 @@ gTests.push({ promise = waitForEvent(document, "popupshown"); sendContextMenuClickToWindow(win, 40, 40); yield promise; - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); menuItem = document.getElementById("context-open-image-tab"); ok(menuItem, "menu item exists"); @@ -564,7 +564,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextMenuPositionRange(ContextMenuUI._panel, 265, 280, 175, 190); @@ -579,7 +579,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextMenuPositionRange(ContextMenuUI._panel, 265, 280, 95, 110); @@ -594,7 +594,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextMenuPositionRange(ContextMenuUI._panel, 295, 310, 540, 555); @@ -609,7 +609,7 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextMenuPositionRange(ContextMenuUI._panel, 295, 310, 340, 355); @@ -624,16 +624,87 @@ gTests.push({ yield promise; // should be visible - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); checkContextMenuPositionRange(ContextMenuUI._panel, 265, 280, 110, 125); promise = waitForEvent(document, "popuphidden"); ContextMenuUI.hide(); yield promise; + + Browser.closeTab(Browser.selectedTab, { forceClose: true }); } }); +function reopenSetUp() { + info(chromeRoot + "browser_context_menu_tests_04.html"); + yield addTab(chromeRoot + "browser_context_menu_tests_04.html"); + + // Sometimes the context UI won't actually show up. + // Since we're just normalizing, we don't want waitForCondition + // to cause an orange. + try { + yield waitForCondition(() => ContextUI.isVisible); + ContextUI.dismiss(); + } catch(e) {} +} + +function reopenTearDown() { + let promise = waitForEvent(document, "popuphidden") + ContextMenuUI.hide(); + yield promise; + ok(!ContextMenuUI._menuPopup.visible, "popup is actually hidden"); + + Browser.closeTab(Browser.selectedTab, { forceClose: true }); +} + +function getReopenTest(aElementInputFn, aWindowInputFn) { + return function () { + let win = Browser.selectedTab.browser.contentWindow; + let panel = ContextMenuUI._menuPopup._panel; + + let link1 = win.document.getElementById("text1-link"); + let link2 = win.document.getElementById("text2-link"); + + // Show the menu on link 1 + let showpromise = waitForEvent(panel, "popupshown"); + aElementInputFn(win, link1); + + ok((yield showpromise), "popupshown event fired"); + ok(ContextMenuUI._menuPopup.visible, "initial popup is visible"); + + // Show the menu on link 2 + let hidepromise = waitForEvent(panel, "popuphidden"); + showpromise = waitForEvent(panel, "popupshown"); + aElementInputFn(win, link2); + + ok((yield hidepromise), "popuphidden event fired"); + ok((yield showpromise), "popupshown event fired"); + ok(ContextMenuUI._menuPopup.visible, "popup is still visible"); + + // Hide the menu + hidepromise = waitForEvent(panel, "popuphidden") + aWindowInputFn(win, 10, 10); + + ok((yield hidepromise), "popuphidden event fired"); + ok(!ContextMenuUI._menuPopup.visible, "popup is no longer visible"); + } +} + +gTests.push({ + desc: "bug 856264 - mouse - context menu should reopen on other links", + setUp: reopenSetUp, + tearDown: reopenTearDown, + run: getReopenTest(sendContextMenuMouseClickToElement, sendMouseClick) +}); + +gTests.push({ + desc: "bug 856264 - touch - context menu should reopen on other links", + setUp: reopenSetUp, + tearDown: reopenTearDown, + run: getReopenTest(sendContextMenuClickToElement, sendTap) +}); + function test() { runTests(); } diff --git a/browser/metro/base/tests/mochitest/browser_context_menu_tests_04.html b/browser/metro/base/tests/mochitest/browser_context_menu_tests_04.html new file mode 100644 index 000000000000..c297e3dd66c5 --- /dev/null +++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests_04.html @@ -0,0 +1,15 @@ + + + + + + +
+
+ hello, I'm sorry but I must be going. +
+ + \ No newline at end of file diff --git a/browser/metro/base/tests/mochitest/browser_form_auto_complete.js b/browser/metro/base/tests/mochitest/browser_form_auto_complete.js index cd2eec2d28bf..2be327e4a8ef 100644 --- a/browser/metro/base/tests/mochitest/browser_form_auto_complete.js +++ b/browser/metro/base/tests/mochitest/browser_form_auto_complete.js @@ -29,8 +29,8 @@ function checkAutofillMenuItemContents(aItemList) { let errors = 0; let found = 0; - for (let idx = 0; idx < AutofillMenuUI._commands.childNodes.length; idx++) { - let item = AutofillMenuUI._commands.childNodes[idx]; + for (let idx = 0; idx < AutofillMenuUI.commands.childNodes.length; idx++) { + let item = AutofillMenuUI.commands.childNodes[idx]; let label = item.firstChild.getAttribute("value"); let value = item.getAttribute("data"); if (aItemList.indexOf(value) == -1) { diff --git a/browser/metro/base/tests/mochitest/browser_selection_frame_content.js b/browser/metro/base/tests/mochitest/browser_selection_frame_content.js index f10132ab3e7e..7d0860761fa2 100644 --- a/browser/metro/base/tests/mochitest/browser_selection_frame_content.js +++ b/browser/metro/base/tests/mochitest/browser_selection_frame_content.js @@ -138,7 +138,7 @@ gTests.push({ yield promise; ok(promise && !(promise instanceof Error), "promise error"); - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); let menuItem = document.getElementById("context-copy"); ok(menuItem, "menu item exists"); diff --git a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js index 19102a7bd527..4ce2ae3bea50 100644 --- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js +++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js @@ -78,7 +78,7 @@ gTests.push({ sendContextMenuClickToElement(window, edit); yield waitForEvent(document, "popupshown"); - ok(ContextMenuUI._menuPopup._visible, "is visible"); + ok(ContextMenuUI._menuPopup.visible, "is visible"); let paste = document.getElementById("context-paste"); ok(!paste.hidden, "paste item is visible"); diff --git a/browser/metro/base/tests/mochitest/head.js b/browser/metro/base/tests/mochitest/head.js index 48b2bfcfb190..f1ffea88469a 100644 --- a/browser/metro/base/tests/mochitest/head.js +++ b/browser/metro/base/tests/mochitest/head.js @@ -39,8 +39,8 @@ function setDevPixelEqualToPx() function checkContextUIMenuItemCount(aCount) { let visibleCount = 0; - for (let idx = 0; idx < ContextMenuUI._commands.childNodes.length; idx++) { - if (!ContextMenuUI._commands.childNodes[idx].hidden) + for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) { + if (!ContextMenuUI.commands.childNodes[idx].hidden) visibleCount++; } is(visibleCount, aCount, "command list count"); @@ -49,8 +49,8 @@ function checkContextUIMenuItemCount(aCount) function checkContextUIMenuItemVisibility(aVisibleList) { let errors = 0; - for (let idx = 0; idx < ContextMenuUI._commands.childNodes.length; idx++) { - let item = ContextMenuUI._commands.childNodes[idx]; + for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) { + let item = ContextMenuUI.commands.childNodes[idx]; if (aVisibleList.indexOf(item.id) != -1 && item.hidden) { // item should be visible errors++; @@ -551,6 +551,20 @@ function logicalCoordsForElement (aElement, aX, aY) { return coords; } +function sendContextMenuMouseClickToElement(aWindow, aElement, aX, aY) { + let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let coords = logicalCoordsForElement(aElement, aX, aY); + + utils.sendMouseEventToWindow("mousedown", coords.x, coords.y, 2, 1, 0); + utils.sendMouseEventToWindow("mouseup", coords.x, coords.y, 2, 1, 0); + utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0); +} + +function sendMouseClick(aWindow, aX, aY) { + EventUtils.synthesizeMouseAtPoint(aX, aY, {}, aWindow); +} + /* * sendContextMenuClick - simulates a press-hold touch input event. Event * is delivered to the main window of the application through the top-level From c0b2132b542b53a7a6e0bbc06657dd2dc821743c Mon Sep 17 00:00:00 2001 From: Jonathan Wilde Date: Thu, 1 Aug 2013 16:54:27 -0700 Subject: [PATCH 20/40] Bug 856264 - Part 2 - Ensure that menus get reshown properly across input methods. r=jimm --HG-- extra : rebase_source : 45d8febeff971a8dd8454a242888267068d03878 --- browser/metro/base/content/helperui/MenuUI.js | 163 +++++++++--------- .../mochitest/browser_context_menu_tests.js | 2 +- 2 files changed, 83 insertions(+), 82 deletions(-) diff --git a/browser/metro/base/content/helperui/MenuUI.js b/browser/metro/base/content/helperui/MenuUI.js index 47b6cd291329..cdf427528bca 100644 --- a/browser/metro/base/content/helperui/MenuUI.js +++ b/browser/metro/base/content/helperui/MenuUI.js @@ -12,7 +12,7 @@ var AutofillMenuUI = { get _panel() { return document.getElementById("autofill-container"); }, get _popup() { return document.getElementById("autofill-popup"); }, - get _commands() { return this._popup.childNodes[0]; }, + get commands() { return this._popup.childNodes[0]; }, get _menuPopup() { if (!this.__menuPopup) { @@ -32,8 +32,8 @@ var AutofillMenuUI = { }, _emptyCommands: function _emptyCommands() { - while (this._commands.firstChild) - this._commands.removeChild(this._commands.firstChild); + while (this.commands.firstChild) + this.commands.removeChild(this.commands.firstChild); }, _positionOptions: function _positionOptions() { @@ -57,7 +57,7 @@ var AutofillMenuUI = { label.setAttribute("value", aSuggestionsList[idx].label); item.setAttribute("data", aSuggestionsList[idx].value); item.appendChild(label); - this._commands.appendChild(item); + this.commands.appendChild(item); } this._menuPopup.show(this._positionOptions()); @@ -65,7 +65,7 @@ var AutofillMenuUI = { selectByIndex: function mn_selectByIndex(aIndex) { this._menuPopup.hide(); - FormHelperUI.doAutoComplete(this._commands.childNodes[aIndex].getAttribute("data")); + FormHelperUI.doAutoComplete(this.commands.childNodes[aIndex].getAttribute("data")); }, hide: function hide () { @@ -85,7 +85,7 @@ var ContextMenuUI = { get _panel() { return document.getElementById("context-container"); }, get _popup() { return document.getElementById("context-popup"); }, - get _commands() { return this._popup.childNodes[0]; }, + get commands() { return this._popup.childNodes[0]; }, get _menuPopup() { if (!this.__menuPopup) { @@ -153,12 +153,12 @@ var ContextMenuUI = { contentTypes.indexOf("selected-text") != -1)) multipleMediaTypes = true; - for (let command of Array.slice(this._commands.childNodes)) { + for (let command of Array.slice(this.commands.childNodes)) { command.hidden = true; } let optionsAvailable = false; - for (let command of Array.slice(this._commands.childNodes)) { + for (let command of Array.slice(this.commands.childNodes)) { let types = command.getAttribute("type").split(","); let lowPriority = (command.hasAttribute("priority") && command.getAttribute("priority") == "low"); @@ -221,7 +221,7 @@ var MenuControlUI = { get _panel() { return document.getElementById("menucontrol-container"); }, get _popup() { return document.getElementById("menucontrol-popup"); }, - get _commands() { return this._popup.childNodes[0]; }, + get commands() { return this._popup.childNodes[0]; }, get _menuPopup() { if (!this.__menuPopup) { @@ -240,8 +240,8 @@ var MenuControlUI = { }, _emptyCommands: function _emptyCommands() { - while (this._commands.firstChild) - this._commands.removeChild(this._commands.firstChild); + while (this.commands.firstChild) + this.commands.removeChild(this.commands.firstChild); }, _positionOptions: function _positionOptions() { @@ -314,7 +314,7 @@ var MenuControlUI = { label.setAttribute("value", child.label); item.appendChild(label); - this._commands.appendChild(item); + this.commands.appendChild(item); } this._menuPopup.show(this._positionOptions()); @@ -338,84 +338,25 @@ function MenuPopup(aPanel, aPopup) { this._panel = aPanel; this._popup = aPopup; this._wantTypeBehind = false; - this._willReshowPopup = false; window.addEventListener('MozAppbarShowing', this, false); } MenuPopup.prototype = { - get _visible() { return !this._panel.hidden; }, - get _commands() { return this._popup.childNodes[0]; }, + get visible() { return !this._panel.hidden; }, + get commands() { return this._popup.childNodes[0]; }, show: function (aPositionOptions) { - if (this._visible) { - this._willReshowPopup = true; - let self = this; - this._panel.addEventListener("transitionend", function () { - self._show(aPositionOptions); - self._panel.removeEventListener("transitionend", arguments.callee); - }); + if (this.visible) { + this._animateHide().then(() => this._animateShow(aPositionOptions)); } else { - this._show(aPositionOptions); + this._animateShow(aPositionOptions); } }, - _show: function (aPositionOptions) { - window.addEventListener("keypress", this, true); - window.addEventListener("mousedown", this, true); - Elements.stack.addEventListener("PopupChanged", this, false); - Elements.browsers.addEventListener("PanBegin", this, false); - - this._panel.hidden = false; - this._position(aPositionOptions || {}); - - let self = this; - this._panel.addEventListener("transitionend", function () { - self._panel.removeEventListener("transitionend", arguments.callee); - self._panel.removeAttribute("showingfrom"); - - let eventName = self._willReshowPopup ? "popupmoved" : "popupshown"; - let event = document.createEvent("Events"); - event.initEvent(eventName, true, false); - self._panel.dispatchEvent(event); - - self._willReshowPopup = false; - }); - - let popupFrom = !aPositionOptions.bottomAligned ? "above" : "below"; - this._panel.setAttribute("showingfrom", popupFrom); - - // Ensure the panel actually gets shifted before getting animated - setTimeout(function () { - self._panel.setAttribute("showing", "true"); - }, 0); - }, - hide: function () { - if (!this._visible) - return; - - window.removeEventListener("keypress", this, true); - window.removeEventListener("mousedown", this, true); - Elements.stack.removeEventListener("PopupChanged", this, false); - Elements.browsers.removeEventListener("PanBegin", this, false); - - let self = this; - this._panel.addEventListener("transitionend", function () { - self._panel.removeEventListener("transitionend", arguments.callee); - self._panel.removeAttribute("hiding"); - self._panel.hidden = true; - self._popup.style.maxWidth = "none"; - self._popup.style.maxHeight = "none"; - - if (!self._willReshowPopup) { - let event = document.createEvent("Events"); - event.initEvent("popuphidden", true, false); - self._panel.dispatchEvent(event); - } - }); - - this._panel.setAttribute("hiding", "true"); - setTimeout(()=>this._panel.removeAttribute("showing"), 0); + if (this.visible) { + this._animateHide(); + } }, _position: function _position(aPositionOptions) { @@ -440,7 +381,7 @@ MenuPopup.prototype = { // Add padding on the side of the menu per the user's hand preference let leftHand = MetroUtils.handPreference == MetroUtils.handPreferenceLeft; if (aSource && aSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) { - this._commands.setAttribute("left-hand", leftHand); + this.commands.setAttribute("left-hand", leftHand); } if (aPositionOptions.rightAligned) @@ -486,6 +427,66 @@ MenuPopup.prototype = { } }, + _animateShow: function (aPositionOptions) { + let deferred = Promise.defer(); + + window.addEventListener("keypress", this, true); + window.addEventListener("click", this, true); + Elements.stack.addEventListener("PopupChanged", this, false); + Elements.browsers.addEventListener("PanBegin", this, false); + + this._panel.hidden = false; + let popupFrom = !aPositionOptions.bottomAligned ? "above" : "below"; + this._panel.setAttribute("showingfrom", popupFrom); + + // This triggers a reflow, which sets transitionability. + // All animation/transition setup must happen before here. + this._position(aPositionOptions || {}); + + let self = this; + this._panel.addEventListener("transitionend", function popupshown () { + self._panel.removeEventListener("transitionend", popupshown); + self._panel.removeAttribute("showingfrom"); + + self._dispatch("popupshown"); + deferred.resolve(); + }); + + this._panel.setAttribute("showing", "true"); + return deferred.promise; + }, + + _animateHide: function () { + let deferred = Promise.defer(); + + window.removeEventListener("keypress", this, true); + window.removeEventListener("click", this, true); + Elements.stack.removeEventListener("PopupChanged", this, false); + Elements.browsers.removeEventListener("PanBegin", this, false); + + let self = this; + this._panel.addEventListener("transitionend", function popuphidden() { + self._panel.removeEventListener("transitionend", popuphidden); + self._panel.removeAttribute("hiding"); + self._panel.hidden = true; + self._popup.style.maxWidth = "none"; + self._popup.style.maxHeight = "none"; + + self._dispatch("popuphidden"); + deferred.resolve(); + }); + + this._panel.setAttribute("hiding", "true"); + this._panel.removeAttribute("showing"); + return deferred.promise; + }, + + _dispatch: function _dispatch(aName) { + let event = document.createEvent("Events"); + event.initEvent(aName, true, false); + this._panel.dispatchEvent(event); + }, + handleEvent: function handleEvent(aEvent) { switch (aEvent.type) { case "keypress": @@ -497,7 +498,7 @@ MenuPopup.prototype = { this.hide(); } break; - case "mousedown": + case "click": if (!this._popup.contains(aEvent.target)) { aEvent.stopPropagation(); this.hide(); diff --git a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js index 11b0dfbc8e06..2eda2a7decfd 100644 --- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js +++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js @@ -642,7 +642,7 @@ function reopenSetUp() { // Sometimes the context UI won't actually show up. // Since we're just normalizing, we don't want waitForCondition - // to cause an orange. + // to cause an orange, so we're putting a try/catch here. try { yield waitForCondition(() => ContextUI.isVisible); ContextUI.dismiss(); From 6729d618abaf337c64c60340f3fbaa8f0d6731eb Mon Sep 17 00:00:00 2001 From: Jonathan Wilde Date: Thu, 8 Aug 2013 12:19:55 -0700 Subject: [PATCH 21/40] Bug 895873 - Re-enable browser_context_menu_tests.js. Intermittent oranges likely fixed in bug 856264. r=jimm --HG-- extra : rebase_source : c2e731fd5b061eda420e60cf959c39346df65003 --- browser/metro/base/tests/mochitest/Makefile.in | 13 +++++-------- .../tests/mochitest/browser_context_menu_tests.js | 10 ++++++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/browser/metro/base/tests/mochitest/Makefile.in b/browser/metro/base/tests/mochitest/Makefile.in index 998bcc29239a..e1235e54a2f9 100644 --- a/browser/metro/base/tests/mochitest/Makefile.in +++ b/browser/metro/base/tests/mochitest/Makefile.in @@ -10,20 +10,17 @@ relativesrcdir = @relativesrcdir@ include $(DEPTH)/config/autoconf.mk -# Disabled for intermittent failures -# Bug 880739 -# browser_context_menu_tests.js \ -# browser_context_menu_tests_01.html \ -# browser_context_menu_tests_02.html \ -# browser_context_menu_tests_03.html \ -# browser_context_menu_tests_04.html \ - MOCHITEST_METRO_FILES = \ head.js \ browser_urlbar.js \ browser_bookmarks.js \ browser_canonizeURL.js \ browser_circular_progress_indicator.js \ + browser_context_menu_tests.js \ + browser_context_menu_tests_01.html \ + browser_context_menu_tests_02.html \ + browser_context_menu_tests_03.html \ + browser_context_menu_tests_04.html \ browser_context_ui.js \ browser_downloads.js \ browser_findbar.js \ diff --git a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js index 2eda2a7decfd..0b551b77ddb9 100644 --- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js +++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js @@ -352,6 +352,9 @@ gTests.push({ } }); +/* +XXX code used to diagnose bug 880739 + var observeLogger = { observe: function (aSubject, aTopic, aData) { info("observeLogger: " + aTopic); @@ -383,15 +386,18 @@ var observeLogger = { Services.obs.removeObserver(observeLogger, "dl-cancel"); } } +*/ // Image context menu tests gTests.push({ desc: "image context menu", setUp: function() { - observeLogger.init(); + // XXX code used to diagnose bug 880739 + //observeLogger.init(); }, tearDown: function() { - observeLogger.shutdown(); + // XXX code used to diagnose bug 880739 + //observeLogger.shutdown(); }, run: function test() { info(chromeRoot + "browser_context_menu_tests_01.html"); From a459f7acd26e887cb4c809b18f8827e7e4eec500 Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Thu, 8 Aug 2013 09:52:01 +1000 Subject: [PATCH 22/40] Bug 897811 - fix uses of browser.pagethumbnails.capturing_disabled to reflect the pref controls if it is *disabled* rather than enabled. r=gavin --- content/base/test/test_ipc_messagemanager_blob.html | 2 +- content/media/webspeech/synth/ipc/test/test_ipc.html | 2 +- dom/browser-element/mochitest/browserElementTestHelpers.js | 4 ++-- dom/devicestorage/ipc/test_ipc.html | 2 +- layout/tools/reftest/reftest-cmdline.js | 2 +- testing/profiles/prefs_general.js | 2 +- toolkit/components/thumbnails/PageThumbs.jsm | 2 +- toolkit/components/thumbnails/test/head.js | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/content/base/test/test_ipc_messagemanager_blob.html b/content/base/test/test_ipc_messagemanager_blob.html index c0df9137d213..7c1e2baddd9d 100644 --- a/content/base/test/test_ipc_messagemanager_blob.html +++ b/content/base/test/test_ipc_messagemanager_blob.html @@ -96,7 +96,7 @@ "set": [ ["dom.ipc.browser_frames.oop_by_default", true], ["dom.mozBrowserFramesEnabled", true], - ["browser.pagethumbnails.capturing_disabled", false] + ["browser.pagethumbnails.capturing_disabled", true] ] }, runTests); }); diff --git a/content/media/webspeech/synth/ipc/test/test_ipc.html b/content/media/webspeech/synth/ipc/test/test_ipc.html index bb2fd49a7d72..99bccdf3420a 100644 --- a/content/media/webspeech/synth/ipc/test/test_ipc.html +++ b/content/media/webspeech/synth/ipc/test/test_ipc.html @@ -181,7 +181,7 @@ ["dom.ipc.browser_frames.oop_by_default", true], ["dom.mozBrowserFramesEnabled", true], - ["browser.pagethumbnails.capturing_disabled", false] + ["browser.pagethumbnails.capturing_disabled", true] ] }, runTests); }); diff --git a/dom/browser-element/mochitest/browserElementTestHelpers.js b/dom/browser-element/mochitest/browserElementTestHelpers.js index 94bec68cac60..5b1f40c7fbac 100644 --- a/dom/browser-element/mochitest/browserElementTestHelpers.js +++ b/dom/browser-element/mochitest/browserElementTestHelpers.js @@ -206,7 +206,7 @@ function expectMozbrowserEvent(iframe, eventName) { // Set some prefs: // -// * browser.pagethumbnails.capturing_disabled: false +// * browser.pagethumbnails.capturing_disabled: true // // Disable tab view; it seriously messes us up. // @@ -244,7 +244,7 @@ function expectMozbrowserEvent(iframe, eventName) { browserElementTestHelpers.lockTestReady(); SpecialPowers.setBoolPref("network.disable.ipc.security", true); - SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", false], + SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", true], ["dom.ipc.browser_frames.oop_by_default", oop], ["dom.ipc.tabs.disabled", false], ["security.mixed_content.block_active_content", false]]}, diff --git a/dom/devicestorage/ipc/test_ipc.html b/dom/devicestorage/ipc/test_ipc.html index 12a5df835255..068b83a9f32a 100644 --- a/dom/devicestorage/ipc/test_ipc.html +++ b/dom/devicestorage/ipc/test_ipc.html @@ -158,7 +158,7 @@ ["dom.ipc.browser_frames.oop_by_default", true], ["dom.mozBrowserFramesEnabled", true], - ["browser.pagethumbnails.capturing_disabled", false] + ["browser.pagethumbnails.capturing_disabled", true] ] }, runTests); }); diff --git a/layout/tools/reftest/reftest-cmdline.js b/layout/tools/reftest/reftest-cmdline.js index 3005a38374ea..c2a615c89ae7 100644 --- a/layout/tools/reftest/reftest-cmdline.js +++ b/layout/tools/reftest/reftest-cmdline.js @@ -106,7 +106,7 @@ RefTestCmdLineHandler.prototype = // Setting this pref makes tests run much faster there. branch.setBoolPref("security.fileuri.strict_origin_policy", false); // Disable the thumbnailing service - branch.setBoolPref("browser.pagethumbnails.capturing_disabled", false); + branch.setBoolPref("browser.pagethumbnails.capturing_disabled", true); var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(nsIWindowWatcher); diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index 4e1d5d1323bd..2a3dc23518f7 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -147,4 +147,4 @@ user_pref("geo.provider.testing", true); // Background thumbnails in particular cause grief, and disabling thumbnails // in general can't hurt - we re-enable them when tests need them. -user_pref("browser.pagethumbnails.capturing_disabled", false); +user_pref("browser.pagethumbnails.capturing_disabled", true); diff --git a/toolkit/components/thumbnails/PageThumbs.jsm b/toolkit/components/thumbnails/PageThumbs.jsm index b7aee14c4d14..647ab27e1911 100644 --- a/toolkit/components/thumbnails/PageThumbs.jsm +++ b/toolkit/components/thumbnails/PageThumbs.jsm @@ -465,7 +465,7 @@ this.PageThumbs = { _prefEnabled: function PageThumbs_prefEnabled() { try { - return Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled"); + return !Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled"); } catch (e) { return true; diff --git a/toolkit/components/thumbnails/test/head.js b/toolkit/components/thumbnails/test/head.js index d0688135e689..77047a38b051 100644 --- a/toolkit/components/thumbnails/test/head.js +++ b/toolkit/components/thumbnails/test/head.js @@ -11,7 +11,7 @@ let {PageThumbs, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp; Cu.import("resource://gre/modules/PlacesUtils.jsm"); let oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled"); -Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", true); +Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false); registerCleanupFunction(function () { while (gBrowser.tabs.length > 1) From f1a4b57f9ce6943f5fd170b709b575a26fe724e6 Mon Sep 17 00:00:00 2001 From: Chris Kitching Date: Thu, 8 Aug 2013 18:51:27 -0700 Subject: [PATCH 23/40] Bug 902932 - Cleaning up problematic dead code in aboutAddons.js r=liuche --- mobile/android/chrome/content/aboutAddons.js | 37 -------------------- 1 file changed, 37 deletions(-) diff --git a/mobile/android/chrome/content/aboutAddons.js b/mobile/android/chrome/content/aboutAddons.js index 9c27dc56abbb..027215005ac1 100644 --- a/mobile/android/chrome/content/aboutAddons.js +++ b/mobile/android/chrome/content/aboutAddons.js @@ -41,7 +41,6 @@ var ContextMenus = { document.getElementById("contextmenu-enable").setAttribute("hidden", "true"); document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true"); - document.getElementById("contextmenu-default").setAttribute("hidden", "true"); return; } @@ -60,8 +59,6 @@ var ContextMenus = { document.getElementById("contextmenu-enable").removeAttribute("hidden"); document.getElementById("contextmenu-disable").setAttribute("hidden", "true"); } - - document.getElementById("contextmenu-default").setAttribute("hidden", "true"); }, enable: function(event) { @@ -253,37 +250,6 @@ var Addons = { list.appendChild(item); } - // Load the search engines - let defaults = Services.search.getDefaultEngines({ }).map(function (e) e.name); - function isDefault(aEngine) - defaults.indexOf(aEngine.name) != -1 - - let defaultDescription = gStringBundle.GetStringFromName("addonsSearchEngine.description"); - - let engines = Services.search.getEngines({ }); - for (let e = 0; e < engines.length; e++) { - let engine = engines[e]; - let addon = {}; - addon.id = engine.name; - addon.type = "search"; - addon.name = engine.name; - addon.version = ""; - addon.description = engine.description || defaultDescription; - addon.iconURL = engine.iconURI ? engine.iconURI.spec : ""; - addon.optionsURL = ""; - addon.appDisabled = false; - addon.scope = isDefault(engine) ? AddonManager.SCOPE_APPLICATION : AddonManager.SCOPE_PROFILE; - addon.engine = engine; - - let item = self._createItem(addon); - item.setAttribute("isDisabled", engine.hidden); - item.setAttribute("updateable", "false"); - item.setAttribute("opType", ""); - item.setAttribute("optionsURL", ""); - item.addon = addon; - list.appendChild(item); - } - // Add a "Browse all Firefox Add-ons" item to the bottom of the list. let browseItem = self._createBrowseItem(); list.appendChild(browseItem); @@ -348,9 +314,6 @@ var Addons = { else uninstallBtn.removeAttribute("disabled"); - let defaultButton = document.getElementById("default-btn"); - defaultButton.setAttribute("hidden", "true"); - let box = document.querySelector("#addons-details > .addon-item .options-box"); box.innerHTML = ""; From 8b6453c632e88806269f3f1f5dae5475cbdeabcd Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:05:05 +0200 Subject: [PATCH 24/40] Backed out changeset 7e0356305566 (bug 894811) --- browser/metro/base/content/bindings/urlbar.xml | 14 +++++--------- browser/metro/base/content/browser-ui.js | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/browser/metro/base/content/bindings/urlbar.xml b/browser/metro/base/content/bindings/urlbar.xml index 83e54dc998d8..bc60a14df681 100644 --- a/browser/metro/base/content/bindings/urlbar.xml +++ b/browser/metro/base/content/bindings/urlbar.xml @@ -281,22 +281,20 @@ @@ -315,9 +313,8 @@ if (this.focused) this.blur(); - if (aShouldRevert) { + if (aShouldRevert) this.value = this._lastKnownGoodURL; - } ]]> @@ -448,7 +445,6 @@ diff --git a/browser/metro/base/content/browser-ui.js b/browser/metro/base/content/browser-ui.js index 3bb27be9cd77..f3b4ee49e6cc 100644 --- a/browser/metro/base/content/browser-ui.js +++ b/browser/metro/base/content/browser-ui.js @@ -1075,7 +1075,6 @@ var BrowserUI = { case "cmd_newTab": this.newTab(null, null, true); this._edit.beginEditing(false); - this._edit.select(); break; case "cmd_closeTab": this.closeTab(); From b6015062963abe6d0c8a1afe1a9a1aa63c1b5755 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:20:02 +0200 Subject: [PATCH 25/40] Backed out changeset 607b35c777f0 (bug 853388) --- toolkit/mozapps/extensions/DeferredSave.jsm | 6 +- toolkit/mozapps/extensions/XPIProvider.jsm | 9 +- .../mozapps/extensions/XPIProviderUtils.js | 92 ++-- .../extensions/test/xpcshell/head_addons.js | 25 +- .../xpcshell/test_ChromeManifestParser.js | 2 +- .../test/xpcshell/test_DeferredSave.js | 8 +- .../test/xpcshell/test_blocklistchange.js | 13 +- .../test/xpcshell/test_bootstrap.js | 91 ++-- .../test/xpcshell/test_bug324121.js | 4 +- .../test/xpcshell/test_bug335238.js | 4 +- .../test/xpcshell/test_bug371495.js | 2 +- .../test/xpcshell/test_bug384052.js | 2 +- .../test/xpcshell/test_bug397778.js | 16 +- .../test/xpcshell/test_bug425657.js | 2 +- .../test/xpcshell/test_bug455906.js | 12 +- .../test/xpcshell/test_bug470377_3.js | 2 +- .../xpcshell/test_bug470377_3_strictcompat.js | 2 +- .../test/xpcshell/test_bug470377_4.js | 2 +- .../test/xpcshell/test_bug521905.js | 2 +- .../test/xpcshell/test_bug526598.js | 6 +- .../test/xpcshell/test_bug541420.js | 2 +- .../test/xpcshell/test_bug542391.js | 8 +- .../test/xpcshell/test_bug559800.js | 8 +- .../test/xpcshell/test_bug563256.js | 6 +- .../test/xpcshell/test_bug564030.js | 6 +- .../test/xpcshell/test_bug566626.js | 2 +- .../test/xpcshell/test_bug567184.js | 2 +- .../test/xpcshell/test_bug569138.js | 2 +- .../test/xpcshell/test_bug576735.js | 6 +- .../test/xpcshell/test_bug587088.js | 22 +- .../test/xpcshell/test_bug595573.js | 2 +- .../test/xpcshell/test_bug596607.js | 2 +- .../test/xpcshell/test_bug616841.js | 2 +- .../test/xpcshell/test_bug655254.js | 10 +- .../test/xpcshell/test_bug659772.js | 4 +- .../test/xpcshell/test_bug675371.js | 6 +- .../test/xpcshell/test_bug740612.js | 2 +- .../test/xpcshell/test_bug753900.js | 2 +- .../test/xpcshell/test_bug757663.js | 2 +- .../test/xpcshell/test_cacheflush.js | 2 +- .../test/xpcshell/test_checkcompatibility.js | 2 +- .../test/xpcshell/test_dictionary.js | 42 +- .../extensions/test/xpcshell/test_disable.js | 18 +- .../test/xpcshell/test_distribution.js | 6 +- .../extensions/test/xpcshell/test_dss.js | 21 +- .../test/xpcshell/test_duplicateplugins.js | 6 +- .../extensions/test/xpcshell/test_error.js | 2 +- .../test/xpcshell/test_filepointer.js | 34 +- .../extensions/test/xpcshell/test_fuel.js | 2 +- .../extensions/test/xpcshell/test_general.js | 7 +- .../test/xpcshell/test_getresource.js | 7 +- .../test/xpcshell/test_hasbinarycomponents.js | 2 +- .../extensions/test/xpcshell/test_hotfix.js | 16 +- .../extensions/test/xpcshell/test_install.js | 78 ++-- .../test/xpcshell/test_install_icons.js | 2 +- .../xpcshell/test_install_strictcompat.js | 36 +- .../extensions/test/xpcshell/test_langpack.js | 22 +- .../extensions/test/xpcshell/test_locale.js | 27 +- .../extensions/test/xpcshell/test_locked.js | 378 ++++++++--------- .../extensions/test/xpcshell/test_locked2.js | 227 +++++----- .../test/xpcshell/test_locked_strictcompat.js | 397 ++++++++---------- .../extensions/test/xpcshell/test_manifest.js | 2 +- .../test/xpcshell/test_mapURIToAddonID.js | 6 +- .../extensions/test/xpcshell/test_migrate1.js | 2 +- .../extensions/test/xpcshell/test_migrate2.js | 2 +- .../extensions/test/xpcshell/test_migrate3.js | 2 +- .../extensions/test/xpcshell/test_migrate5.js | 2 +- .../test_onPropertyChanged_appDisabled.js | 2 +- .../test/xpcshell/test_pluginchange.js | 2 +- .../extensions/test/xpcshell/test_plugins.js | 2 +- .../extensions/test/xpcshell/test_registry.js | 2 +- .../extensions/test/xpcshell/test_safemode.js | 6 +- .../extensions/test/xpcshell/test_startup.js | 6 +- .../extensions/test/xpcshell/test_syncGUID.js | 4 +- .../test/xpcshell/test_targetPlatforms.js | 2 +- .../extensions/test/xpcshell/test_theme.js | 38 +- .../test/xpcshell/test_uninstall.js | 8 +- .../extensions/test/xpcshell/test_update.js | 30 +- .../test/xpcshell/test_update_strictcompat.js | 14 +- .../extensions/test/xpcshell/test_updateid.js | 33 +- .../extensions/test/xpcshell/test_upgrade.js | 2 +- .../xpcshell/test_upgrade_strictcompat.js | 2 +- 82 files changed, 907 insertions(+), 994 deletions(-) mode change 100755 => 100644 toolkit/mozapps/extensions/test/xpcshell/test_locked.js diff --git a/toolkit/mozapps/extensions/DeferredSave.jsm b/toolkit/mozapps/extensions/DeferredSave.jsm index 298a4f3a5609..20a75fe83203 100644 --- a/toolkit/mozapps/extensions/DeferredSave.jsm +++ b/toolkit/mozapps/extensions/DeferredSave.jsm @@ -73,14 +73,10 @@ function DeferredSave(aPath, aDataProvider, aDelay) { // Some counters for telemetry // The total number of times the file was written this.totalSaves = 0; - // The number of times the data became dirty while // another save was in progress this.overlappedSaves = 0; - // Error returned by the most recent write (if any) - this._lastError = null; - if (aDelay && (aDelay > 0)) this._delay = aDelay; else @@ -92,7 +88,7 @@ DeferredSave.prototype = { return this._pending || this.writeInProgress; }, - get lastError() { + get error() { return this._lastError; }, diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 98cbeef518f8..61ef77ed3fc7 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -1884,9 +1884,9 @@ var XPIProvider = { delete this._uriMappings; if (gLazyObjectsLoaded) { - XPIDatabase.shutdown(function shutdownCallback(saveError) { + XPIDatabase.shutdown(function shutdownCallback() { LOG("Notifying XPI shutdown observers"); - Services.obs.notifyObservers(null, "xpi-provider-shutdown", saveError); + Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); }); } else { @@ -2594,8 +2594,9 @@ var XPIProvider = { aOldAddon.descriptor = aAddonState.descriptor; aOldAddon.visible = !(aOldAddon.id in visibleAddons); - XPIDatabase.saveChanges(); + // Update the database + XPIDatabase.setAddonDescriptor(aOldAddon, aAddonState.descriptor); if (aOldAddon.visible) { visibleAddons[aOldAddon.id] = aOldAddon; @@ -3187,6 +3188,8 @@ var XPIProvider = { // If the database doesn't exist and there are add-ons installed then we // must update the database however if there are no add-ons then there is // no need to update the database. + // Avoid using XPIDatabase.dbFileExists, as that code is lazy-loaded, + // and we want to avoid loading it until absolutely necessary. let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); if (!dbFile.exists()) updateDatabase = state.length > 0; diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index ad36119d54bd..89ce9fac9c0d 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -349,6 +349,7 @@ function DBAddonInternal(aLoaded) { // this change is being detected. } + // XXX Can we redesign pendingUpgrade? XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", function DBA_pendingUpgradeGetter() { for (let install of XPIProvider.installs) { @@ -425,17 +426,17 @@ this.XPIDatabase = { migrateData: null, // Active add-on directories loaded from extensions.ini and prefs at startup. activeBundles: null, + // Special handling for when the database is locked at first load + lockedDatabase: false, - // Saved error object if we fail to read an existing database - _loadError: null, - - // Error reported by our most recent attempt to read or write the database, if any - get lastError() { - if (this._loadError) - return this._loadError; - if (this._deferredSave) - return this._deferredSave.lastError; - return null; + // XXX may be able to refactor this away + get dbfileExists() { + delete this.dbfileExists; + return this.dbfileExists = this.jsonFile.exists(); + }, + set dbfileExists(aValue) { + delete this.dbfileExists; + return this.dbfileExists = aValue; }, /** @@ -446,6 +447,11 @@ this.XPIDatabase = { throw new Error("Attempt to use XPI database when it is not initialized"); } + // handle the "in memory only" case + if (this.lockedDatabase) { + return; + } + if (!this._deferredSave) { this._deferredSave = new DeferredSave(this.jsonFile.path, () => JSON.stringify(this), @@ -461,16 +467,11 @@ this.XPIDatabase = { // save the database. LOG("XPI Database saved, setting schema version preference to " + DB_SCHEMA); Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); - // Reading the DB worked once, so we don't need the load error - this._loadError = null; }, error => { // Need to try setting the schema version again later this._schemaVersionSet = false; WARN("Failed to save XPI database", error); - // this._deferredSave.lastError has the most recent error so we don't - // need this any more - this._loadError = null; }); } }, @@ -478,15 +479,16 @@ this.XPIDatabase = { flush: function() { // handle the "in memory only" and "saveChanges never called" cases if (!this._deferredSave) { - return Promise.resolve(0); + let done = Promise.defer(); + done.resolve(0); + return done.promise; } return this._deferredSave.flush(); }, /** - * Converts the current internal state of the XPI addon database to - * a JSON.stringify()-ready structure + * Converts the current internal state of the XPI addon database to JSON */ toJSON: function() { if (!this.addonDB) { @@ -494,9 +496,13 @@ this.XPIDatabase = { throw new Error("Attempt to save database without loading it first"); } + let addons = []; + for (let [, addon] of this.addonDB) { + addons.push(addon); + } let toSave = { schemaVersion: DB_SCHEMA, - addons: [...this.addonDB.values()] + addons: addons }; return toSave; }, @@ -619,16 +625,17 @@ this.XPIDatabase = { // Handle mismatched JSON schema version. For now, we assume // compatibility for JSON data, though we throw away any fields we // don't know about + // XXX preserve unknown fields during save/restore LOG("JSON schema mismatch: expected " + DB_SCHEMA + ", actual " + inputAddons.schemaVersion); } // If we got here, we probably have good data // Make AddonInternal instances from the loaded data and save them let addonDB = new Map(); - for (let loadedAddon of inputAddons.addons) { + inputAddons.addons.forEach(function(loadedAddon) { let newAddon = new DBAddonInternal(loadedAddon); addonDB.set(newAddon._key, newAddon); - }; + }); this.addonDB = addonDB; LOG("Successfully read XPI database"); this.initialized = true; @@ -670,14 +677,15 @@ this.XPIDatabase = { /** * Reconstruct when the DB file exists but is unreadable - * (for example because read permission is denied) + * (for example because read permission is denied */ rebuildUnreadableDB: function(aError, aRebuildOnError) { WARN("Extensions database " + this.jsonFile.path + " exists but is not readable; rebuilding in memory", aError); - // Remember the error message until we try and write at least once, so - // we know at shutdown time that there was a problem - this._loadError = aError; + // XXX open question - if we can overwrite at save time, should we, or should we + // leave the locked database in case we can recover from it next time we start up? + // The old code made one attempt to remove the locked file before it rebuilt in memory + this.lockedDatabase = true; // XXX TELEMETRY report when this happens? this.rebuildDatabase(aRebuildOnError); }, @@ -965,26 +973,24 @@ this.XPIDatabase = { shutdown: function XPIDB_shutdown(aCallback) { LOG("shutdown"); if (this.initialized) { - // If our last database I/O had an error, try one last time to save. - if (this.lastError) - this.saveChanges(); + // If we are running with an in-memory database then force a new + // extensions.ini to be written to disk on the next startup + if (this.lockedDatabase) + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); this.initialized = false; + let result = null; // Make sure any pending writes of the DB are complete, and we // finish cleaning up, and then call back this.flush() .then(null, error => { ERROR("Flush of XPI database failed", error); + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + result = error; return 0; }) .then(count => { - // If our last attempt to read or write the DB failed, force a new - // extensions.ini to be written to disk on the next startup - let lastSaveFailed = this.lastError; - if (lastSaveFailed) - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - // Clear out the cached addons data loaded from JSON delete this.addonDB; delete this._dbPromise; @@ -994,7 +1000,7 @@ this.XPIDatabase = { delete this._schemaVersionSet; if (aCallback) - aCallback(lastSaveFailed); + aCallback(result); }); } else { @@ -1335,6 +1341,20 @@ this.XPIDatabase = { this.saveChanges(); }, + /** + * Synchronously sets the file descriptor for an add-on. + * XXX IRVING could replace this with setAddonProperties + * + * @param aAddon + * The DBAddonInternal being updated + * @param aDescriptor + * File path of the installed addon + */ + setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { + aAddon.descriptor = aDescriptor; + this.saveChanges(); + }, + /** * Synchronously updates an add-on's active flag in the database. * @@ -1352,6 +1372,8 @@ this.XPIDatabase = { * Synchronously calculates and updates all the active flags in the database. */ updateActiveAddons: function XPIDB_updateActiveAddons() { + // XXX IRVING this may get called during XPI-utils shutdown + // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); for (let [, addon] of this.addonDB) { let newActive = (addon.visible && !addon.userDisabled && diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 877ce0b6c322..781f38e21c30 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -344,10 +344,6 @@ function do_check_icons(aActual, aExpected) { } } -// Record the error (if any) from trying to save the XPI -// database at shutdown time -let gXPISaveError = null; - /** * Starts up the add-on manager as if it was started by the application. * @@ -400,24 +396,26 @@ function shutdownManager() { if (!gInternalManager) return; + let obs = AM_Cc["@mozilla.org/observer-service;1"]. + getService(AM_Ci.nsIObserverService); + let xpiShutdown = false; - Services.obs.addObserver({ + obs.addObserver({ observe: function(aSubject, aTopic, aData) { xpiShutdown = true; - gXPISaveError = aData; - Services.obs.removeObserver(this, "xpi-provider-shutdown"); + obs.removeObserver(this, "xpi-provider-shutdown"); } }, "xpi-provider-shutdown", false); let repositoryShutdown = false; - Services.obs.addObserver({ + obs.addObserver({ observe: function(aSubject, aTopic, aData) { repositoryShutdown = true; - Services.obs.removeObserver(this, "addon-repository-shutdown"); + obs.removeObserver(this, "addon-repository-shutdown"); } }, "addon-repository-shutdown", false); - Services.obs.notifyObservers(null, "quit-application-granted", null); + obs.notifyObservers(null, "quit-application-granted", null); let scope = Components.utils.import("resource://gre/modules/AddonManager.jsm"); scope.AddonManagerInternal.shutdown(); gInternalManager = null; @@ -430,11 +428,14 @@ function shutdownManager() { // Clear any crash report annotations gAppInfo.annotations = {}; - let thr = Services.tm.mainThread; + let thr = AM_Cc["@mozilla.org/thread-manager;1"]. + getService(AM_Ci.nsIThreadManager). + mainThread; // Wait until we observe the shutdown notifications while (!repositoryShutdown || !xpiShutdown) { - thr.processNextEvent(true); + if (thr.hasPendingEvents()) + thr.processNextEvent(false); } // Force the XPIProvider provider to reload to better diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js b/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js index 2e4adbe0f54d..61ffc12f6703 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js @@ -103,6 +103,6 @@ function run_test_1() { do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i])); } - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js b/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js index b40b5342fb23..7b29fa68d5c6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js @@ -37,8 +37,8 @@ function DeferredSaveTester(aDelay, aDataProvider) { return tester.saver.flush(); }, - get lastError() { - return tester.saver.lastError; + get error() { + return tester.saver.error; } }; @@ -154,11 +154,11 @@ add_task(function test_error_immediate() { count => do_throw("Did not get expected error"), error => do_check_eq(testError.message, error.message) ); - do_check_eq(testError, tester.lastError); + do_check_eq(testError, tester.error); // This write should succeed and clear the error yield tester.save("test_error_immediate succeeds"); - do_check_eq(null, tester.lastError); + do_check_eq(null, tester.error); // The failed save attempt counts in our total do_check_eq(2, tester.saver.totalSaves); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 752e4103740a..d3b8aceb2ccb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -335,12 +335,12 @@ const ADDON_IDS = ["softblock1@tests.mozilla.org", // Don't need the full interface, attempts to call other methods will just // throw which is just fine var WindowWatcher = { - openWindow: function(parent, url, name, features, openArgs) { + openWindow: function(parent, url, name, features, arguments) { // Should be called to list the newly blocklisted items do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG); // Simulate auto-disabling any softblocks - var list = openArgs.wrappedJSObject.list; + var list = arguments.wrappedJSObject.list; list.forEach(function(aItem) { if (!aItem.blocked) aItem.disable = true; @@ -1329,7 +1329,7 @@ add_test(function run_manual_update_2_test() { startupManager(false); - AddonManager.getAddonsByIDs(ADDON_IDS, callback_soon(function([s1, s2, s3, s4, s5, h, r]) { + AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1347,8 +1347,7 @@ add_test(function run_manual_update_2_test() { manual_update("2", function manual_update_2_2() { restartManager(); - AddonManager.getAddonsByIDs(ADDON_IDS, - callback_soon(function([s1, s2, s3, s4, s5, h, r]) { + AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1376,9 +1375,9 @@ add_test(function run_manual_update_2_test() { run_next_test(); }); }); - })); + }); }); - })); + }); }); // Uses the API to install blocked add-ons from the local filesystem diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index b6b3c65f7aaa..912c676f8e14 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -455,7 +455,7 @@ function check_test_7() { do_check_eq(getShutdownNewVersion(), 0); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0"); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_eq(b1, null); restartManager(); @@ -465,7 +465,7 @@ function check_test_7() { do_check_bootstrappedPref(run_test_8); }); - })); + }); } // Test that a bootstrapped extension dropped into the profile loads properly @@ -671,13 +671,10 @@ function run_test_12() { do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); b1.uninstall(); - do_execute_soon(test_12_restart); - }); -} + restartManager(); -function test_12_restart() { - restartManager(); - do_check_bootstrappedPref(run_test_13); + do_check_bootstrappedPref(run_test_13); + }); } @@ -706,7 +703,7 @@ function run_test_13() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_13)); + ], function() {do_execute_soon(check_test_13)}); install.install(); }); } @@ -727,27 +724,23 @@ function check_test_13() { do_check_eq(getActiveVersion(), 0); // Should not have called startup though do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); - do_execute_soon(test_13_restart); - }); - }); -} + restartManager(); -function test_13_restart() { - restartManager(); + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + do_check_neq(b1, null); + do_check_eq(b1.version, "3.0"); + do_check_true(b1.appDisabled); + do_check_false(b1.userDisabled); + do_check_false(b1.isActive); + do_check_eq(getInstalledVersion(), 3); // We call install even for disabled add-ons + do_check_eq(getActiveVersion(), 0); // Should not have called startup though + do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { - do_check_neq(b1, null); - do_check_eq(b1.version, "3.0"); - do_check_true(b1.appDisabled); - do_check_false(b1.userDisabled); - do_check_false(b1.isActive); - do_check_eq(getInstalledVersion(), 3); // We call install even for disabled add-ons - do_check_eq(getActiveVersion(), 0); // Should not have called startup though - do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); - - do_check_bootstrappedPref(function() { - b1.uninstall(); - do_execute_soon(run_test_14); + do_check_bootstrappedPref(function() { + b1.uninstall(); + do_execute_soon(run_test_14); + }); + }); }); }); } @@ -819,7 +812,7 @@ function run_test_15() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_15)); + ], function() {do_execute_soon(check_test_15)}); install.install(); }); }); @@ -861,7 +854,7 @@ function check_test_15() { function run_test_16() { resetPrefs(); waitForPref("bootstraptest.startup_reason", function test_16_after_startup() { - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { // Should have installed and started do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); @@ -879,7 +872,7 @@ function run_test_16() { gAppInfo.inSafeMode = true; startupManager(false); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { // Should still be stopped do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 0); @@ -899,10 +892,10 @@ function run_test_16() { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { b1.uninstall(); - do_execute_soon(run_test_17); + run_test_17(); }); - })); - })); + }); + }); }); installAllFiles([do_get_addon("test_bootstrap1_1")], function() { }); } @@ -1029,7 +1022,7 @@ function run_test_20() { do_check_eq(getInstallOldVersion(), 1); do_check_eq(getStartupOldVersion(), 0); - do_execute_soon(run_test_21); + run_test_21(); }); } @@ -1086,7 +1079,7 @@ function run_test_22() { startupManager(); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { // Should have installed and started do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); @@ -1130,7 +1123,7 @@ function run_test_22() { run_test_23(); }); }); - })); + }); } @@ -1204,17 +1197,17 @@ function check_test_23() { let dir = do_get_addon_root_uri(profileDir, "bootstrap1@tests.mozilla.org"); do_check_eq(b1.getResourceURI("bootstrap.js").spec, dir + "bootstrap.js"); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, function(list) { do_check_eq(list.length, 0); restartManager(); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { b1.uninstall(); restartManager(); testserver.stop(run_test_24); - })); - })); + }); + }); }); }); }); @@ -1281,7 +1274,7 @@ function run_test_25() { do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); @@ -1302,7 +1295,7 @@ function run_test_25() { do_check_bootstrappedPref(run_test_26); }); - })); + }); }); }); installAllFiles([do_get_addon("test_bootstrap1_1")], function test_25_installed() { @@ -1318,7 +1311,7 @@ function run_test_26() { do_check_eq(getInstalledVersion(), 0); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_true(b1.isActive); @@ -1339,7 +1332,7 @@ function run_test_26() { do_check_bootstrappedPref(run_test_27); }); - })); + }); }); } @@ -1362,7 +1355,7 @@ function run_test_27() { do_check_eq(getUninstallNewVersion(), 4); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_false(b1.isActive); @@ -1381,7 +1374,7 @@ function run_test_27() { do_check_bootstrappedPref(run_test_28); }); - })); + }); }); }); } @@ -1397,7 +1390,7 @@ function run_test_28() { do_check_eq(getInstallOldVersion(), 4); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_false(b1.isActive); @@ -1421,7 +1414,7 @@ function run_test_28() { do_check_bootstrappedPref(do_test_finished); }); - })); + }); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js index b88c07b235d9..cb6ede5fc1f4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js @@ -138,7 +138,7 @@ function run_test() { installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() { restartManager(); - AddonManager.getAddonByID(ADDONS[0].id, callback_soon(function(addon) { + AddonManager.getAddonByID(ADDONS[0].id, function(addon) { do_check_true(!(!addon)); addon.userDisabled = true; restartManager(); @@ -168,7 +168,7 @@ function run_test() { } } }); - })); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js index 8bfef2feffdc..c379fcdcdca3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js @@ -165,7 +165,7 @@ function run_test() { installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() { restartManager(); - AddonManager.getAddonByID(ADDONS[1].id, callback_soon(function(addon) { + AddonManager.getAddonByID(ADDONS[1].id, function(addon) { do_check_true(!(!addon)); addon.userDisabled = true; restartManager(); @@ -176,6 +176,6 @@ function run_test() { item.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); }); - })); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js index ac746dda7b19..7919b7c171fa 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js @@ -28,7 +28,7 @@ function run_test() do_check_eq(addon.optionsURL, null); do_check_eq(addon.aboutURL, null); - do_execute_soon(do_test_finished); + do_test_finished(); }); }); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js index aeaaf3d8ff2d..bbdcb76a1b0c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js @@ -96,7 +96,7 @@ function run_test() onUpdateFinished: function(addon, error) { do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); do_check_true(gSeenExpectedURL); - do_execute_soon(shutdownTest); + shutdownTest(); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js index aa18a6946ab9..134b17a53bf5 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js @@ -27,7 +27,7 @@ function run_test() } function run_test_1() { - AddonManager.getAddonByID(ID, callback_soon(function(addon) { + AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "fr Name"); do_check_eq(addon.description, "fr Description"); @@ -40,9 +40,9 @@ function run_test_1() { do_check_neq(newAddon, null); do_check_eq(newAddon.name, "fr Name"); - do_execute_soon(run_test_2); + run_test_2(); }); - })); + }); } function run_test_2() { @@ -55,7 +55,7 @@ function run_test_2() { do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - do_execute_soon(run_test_3); + run_test_3(); }); } @@ -69,7 +69,7 @@ function run_test_3() { do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - do_execute_soon(run_test_4); + run_test_4(); }); } @@ -83,7 +83,7 @@ function run_test_4() { do_check_eq(addon.name, "es-ES Name"); do_check_eq(addon.description, "es-ES Description"); - do_execute_soon(run_test_5); + run_test_5(); }); } @@ -97,7 +97,7 @@ function run_test_5() { if (addon.name != "zh-TW Name" && addon.name != "zh-CN Name") do_throw("zh matched to " + addon.name); - do_execute_soon(run_test_6); + run_test_6(); }); } @@ -112,6 +112,6 @@ function run_test_6() { do_check_eq(addon.name, "en Name"); do_check_eq(addon.description, "en Description"); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js index f11a942fb68e..f383b201449a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js @@ -21,7 +21,7 @@ function run_test() do_check_eq(addon.name, "Deutsches W\u00f6rterbuch"); do_check_eq(addon.name.length, 20); - do_execute_soon(do_test_finished); + do_test_finished(); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js index 4ed2df463c3b..fc31a4086229 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js @@ -285,7 +285,7 @@ function check_initial_state(callback) { function check_test_pt1() { dump("Checking pt 1\n"); - AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) { + AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) { for (var i = 0; i < ADDONS.length; i++) { if (!addons[i]) do_throw("Addon " + (i + 1) + " did not get installed correctly"); @@ -316,7 +316,7 @@ function check_test_pt1() { gTestCheck = check_test_pt2; load_blocklist("bug455906_warn.xml"); }); - })); + }); } function check_notification_pt2(args) { @@ -357,7 +357,7 @@ function check_test_pt2() { restartManager(); dump("Checking results pt 2\n"); - AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) { + AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) { // Should have disabled this add-on as requested do_check_eq(check_addon_state(addons[2]), "true,true,false"); do_check_eq(check_plugin_state(PLUGINS[2]), "true,false"); @@ -386,7 +386,7 @@ function check_test_pt2() { gNotificationCheck = null; gTestCheck = run_test_pt3; load_blocklist("bug455906_start.xml"); - })); + }); } function run_test_pt3() { @@ -485,7 +485,7 @@ function check_test_pt3() { } function run_test_pt4() { - AddonManager.getAddonByID(ADDONS[4].id, callback_soon(function(addon) { + AddonManager.getAddonByID(ADDONS[4].id, function(addon) { addon.userDisabled = false; PLUGINS[4].enabledState = Ci.nsIPluginTag.STATE_ENABLED; restartManager(); @@ -494,7 +494,7 @@ function run_test_pt4() { gTestCheck = check_test_pt4; load_blocklist("bug455906_empty.xml"); }); - })); + }); } function check_notification_pt4(args) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js index fcac471ee6db..6dee8ee9e738 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js @@ -90,6 +90,6 @@ function run_test_2() { do_check_neq(a5, null); do_check_true(a5.isActive); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js index 7a3347320e82..e8adc8b07bc0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js @@ -89,6 +89,6 @@ function run_test_2() { do_check_neq(a5, null); do_check_true(a5.isActive); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js index 701cbe4485ee..362ccec5c452 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js @@ -87,6 +87,6 @@ function run_test_2() { do_check_neq(a5, null); do_check_true(a5.isActive); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js index b507fc100125..962e1ebc1ec4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js @@ -54,6 +54,6 @@ function run_test_2() { do_check_neq(addon, null); do_check_false(addon.isActive); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js index debf5917233e..f399dd9d114d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js @@ -16,7 +16,7 @@ function run_test() { AddonManager.getAddonsByIDs(["bug526598_1@tests.mozilla.org", "bug526598_2@tests.mozilla.org"], - callback_soon(function([a1, a2]) { + function([a1, a2]) { do_check_neq(a1, null); do_check_true(a1.hasResource("install.rdf")); @@ -47,8 +47,8 @@ function run_test() { do_check_eq(newa1, null); do_check_eq(newa2, null); - do_execute_soon(do_test_finished); + do_test_finished(); }); - })); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js index 1f70b42d559b..7f3719611ea4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js @@ -31,7 +31,7 @@ function run_test() { if (!("nsIWindowsRegKey" in Components.interfaces)) do_check_true((file.permissions & 0100) == 0100); - do_execute_soon(do_test_finished); + do_test_finished(); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js index 3c1056852a32..17edaf39b78d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -332,7 +332,7 @@ function run_test() { AddonManager.getAddonsByIDs(["bug542391_2@tests.mozilla.org", "bug542391_4@tests.mozilla.org"], - callback_soon(function disable_and_restart([a2, a4]) { + function disable_and_restart([a2, a4]) { do_check_true(a2 != null && a4 != null); a2.userDisabled = true; a4.userDisabled = true; @@ -349,7 +349,7 @@ function run_test() { "bug542391_4@tests.mozilla.org", "bug542391_5@tests.mozilla.org", "bug542391_6@tests.mozilla.org"], - callback_soon(function(addons) { + function(addons) { check_state_v1(addons); WindowWatcher.expected = true; @@ -372,8 +372,8 @@ function run_test() { do_execute_soon(run_test_1); }); - })); - })); + }); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index 41057cd7685f..741debb76b4c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -41,7 +41,7 @@ function end_test() { } function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -50,13 +50,13 @@ function run_test_1() { gExtensionsJSON.remove(true); do_execute_soon(check_test_1); - })); + }); } function check_test_1() { startupManager(false); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -67,5 +67,5 @@ function check_test_1() { do_check_true(gExtensionsJSON.fileSize > 0); end_test(); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js index 2437cf7480ae..79ebdc66d985 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js @@ -65,7 +65,7 @@ function run_test() { } function end_test() { - do_execute_soon(do_test_finished); + do_test_finished(); } // Checks switching to a different theme and back again leaves everything the @@ -196,7 +196,7 @@ function run_test_2() { function check_test_2() { restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "alternate@tests.mozilla.org"], callback_soon(function([d, a]) { + "alternate@tests.mozilla.org"], function([d, a]) { do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "alternate/1.0"); do_check_true(d.userDisabled); @@ -255,5 +255,5 @@ function check_test_2() { end_test(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js index b5ac157c7d99..36c4a2b3c021 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js @@ -28,7 +28,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a) { do_check_neq(a, null); do_check_eq(a.version, "1.0"); do_check_false(a.userDisabled); @@ -57,7 +57,7 @@ function run_test() { do_check_true(a.isActive); do_check_true(isExtensionInAddonsList(profileDir, a.id)); - do_execute_soon(do_test_finished); + do_test_finished(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js index 641ff87c9783..facdfcc838f3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js @@ -106,7 +106,7 @@ function run_test_2() { do_check_eq(a1, gAddon); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js index 0e78630686a9..f905698eedbc 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js @@ -47,7 +47,7 @@ function check_test_1() { do_check_false(b1.userDisabled); do_check_false(b1.isActive); - do_execute_soon(do_test_finished); + do_test_finished(); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js index 4869fc117377..3efb6973fe54 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js @@ -141,7 +141,7 @@ function run_test() { do_check_true(a6.appDisabled); do_check_false(a6.isActive); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js index 007e8270696d..b9452b16296a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js @@ -38,7 +38,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { a1.uninstall(); shutdownManager(); @@ -60,7 +60,7 @@ function run_test() { // Addon2 should have been detected do_check_neq(a2, null); - do_execute_soon(do_test_finished); + do_test_finished(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js index 01de80634c85..6b5b5531e7fa 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js @@ -96,12 +96,12 @@ function run_test_1() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { check_addon_upgrading(a1); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { check_addon_upgrading(a1); fstream.close(); @@ -114,8 +114,8 @@ function run_test_1() { a1.uninstall(); do_execute_soon(run_test_2); }); - })); - })); + }); + }); }); }); }); @@ -128,7 +128,7 @@ function run_test_2() { installAllFiles([do_get_addon("test_bug587088_1")], function() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { check_addon(a1, "1.0"); // Lock either install.rdf for unpacked add-ons or the xpi for packed add-ons. @@ -146,12 +146,12 @@ function run_test_2() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { check_addon_uninstalling(a1, true); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { check_addon_uninstalling(a1, true); fstream.close(); @@ -165,10 +165,10 @@ function run_test_2() { do_check_false(dir.exists()); do_check_false(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org")); - do_execute_soon(do_test_finished); + do_test_finished(); }); - })); - })); - })); + }); + }); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js index 7e2bf7d778fa..dc7d8ebfa69f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js @@ -35,6 +35,6 @@ function run_test_2() { do_check_neq(a1, null); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js index 3e655dc87ed9..2a4da3b88b5b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js @@ -135,6 +135,6 @@ function run_test_3() { do_check_eq(a3, null); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js index d0c9739602b0..722f151f21a9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js @@ -21,6 +21,6 @@ function run_test() { AddonManager.getAddonByID("foo", function(aAddon) { test_string_compare(); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js index 45274b734fed..4adda9d592dd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js @@ -84,7 +84,7 @@ function run_test_1() { onUpdateFinished: function() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_false(a1.appDisabled); do_check_true(a1.isActive); @@ -115,9 +115,9 @@ function run_test_1() { do_check_false(isExtensionInAddonsList(userDir, a2.id)); do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1); - do_execute_soon(run_test_2); + run_test_2(); }); - })); + }); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); @@ -125,7 +125,7 @@ function run_test_1() { //Set up the profile function run_test_2() { - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { do_check_neq(a2, null); do_check_false(a2.appDisabled); do_check_true(a2.isActive); @@ -160,5 +160,5 @@ function run_test_2() { end_test(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index c6e8ab4e2200..46b3e0393d2c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -290,7 +290,7 @@ function run_test_2() { "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4]) { + function([a1, a2, a3, a4]) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); do_check_true(a1.appDisabled); @@ -334,7 +334,7 @@ function run_test_2() { shutdownManager(); do_test_finished(); - })); + }); }; }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js index 579335d8aad3..617e87868ed4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js @@ -24,7 +24,9 @@ function run_test() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test)); + ], function() { + do_execute_soon(check_test) + }); install.install(); }); } @@ -86,6 +88,6 @@ function check_test() { do_check_false(target.active); } - do_execute_soon(do_test_finished); + do_test_finished(); })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js index d17e7acde6f9..e85976751b98 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js @@ -35,6 +35,6 @@ function run_test() { do_check_eq(getInstalledVersion(), "1.0"); do_check_eq(getActiveVersion(), "1.0"); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js index 2068623390bd..6878e329fd4f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js @@ -81,6 +81,6 @@ function run_test() { do_check_neq(a4, null); do_check_in_crash_annotation(addon4.id, addon4.version); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js index 648c7acc3ba4..3fef71a49eb6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js @@ -107,6 +107,6 @@ function run_test_2() { AddonManager.removeInstallListener(listener2); AddonManager.removeInstallListener(listener3); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js index c0615ef0e010..173514fe012f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js @@ -117,6 +117,6 @@ function run_test_4() { do_check_true(gCacheFlushed); gCacheFlushed = false; - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js b/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js index b9fc0b3abaa0..160e605bd552 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js @@ -191,6 +191,6 @@ function run_test_4() { function([a1, a2, a3, a4, a5]) { check_state(false, a1, a2, a3, a4, a5); - do_execute_soon(do_test_finished); + do_test_finished("checkcompatibility.js"); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js index c24b5a1b06e8..ff9c3228a71a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js @@ -149,7 +149,7 @@ function run_test_1() { HunspellEngine.listener = function(aEvent) { HunspellEngine.listener = null; do_check_eq(aEvent, "addDirectory"); - do_execute_soon(check_test_1); + check_test_1(); }; }); install.install(); @@ -317,8 +317,7 @@ function check_test_7() { do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_not_in_crash_annotation("ab-CD@dictionaries.addons.mozilla.org", "1.0"); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { do_check_eq(b1, null); restartManager(); @@ -328,7 +327,7 @@ function check_test_7() { do_execute_soon(run_test_8); }); - })); + }); } // Test that a bootstrapped extension dropped into the profile loads properly @@ -428,8 +427,7 @@ function run_test_16() { installAllFiles([do_get_addon("test_dictionary")], function() { // spin the event loop to let the addon finish starting do_execute_soon(function check_installed_dictionary() { - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { // Should have installed and started do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); @@ -441,8 +439,7 @@ function run_test_16() { gAppInfo.inSafeMode = true; startupManager(false); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { // Should still be stopped do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_false(b1.isActive); @@ -459,8 +456,8 @@ function run_test_16() { do_execute_soon(run_test_17); }); - })); - })); + }); + }); }); }); } @@ -486,8 +483,7 @@ function run_test_17() { startupManager(); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { // Should have installed and started do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_neq(b1, null); @@ -502,7 +498,7 @@ function run_test_17() { restartManager(); run_test_23(); - })); + }); } // Tests that installing from a URL doesn't require a restart @@ -569,7 +565,7 @@ function check_test_23() { let dir = do_get_addon_root_uri(profileDir, "ab-CD@dictionaries.addons.mozilla.org"); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, function(list) { do_check_eq(list.length, 0); restartManager(); @@ -577,7 +573,7 @@ function check_test_23() { b1.uninstall(); do_execute_soon(run_test_25); }); - })); + }); }); }); } @@ -596,8 +592,7 @@ function run_test_25() { // Needs a restart to complete this so the old version stays running do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); @@ -615,7 +610,7 @@ function run_test_25() { do_execute_soon(run_test_26); }); - })); + }); }); }; @@ -629,8 +624,7 @@ function run_test_26() { // Needs a restart to complete this do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "2.0"); do_check_true(b1.isActive); @@ -650,7 +644,7 @@ function run_test_26() { b1.uninstall(); do_execute_soon(run_test_27); }); - })); + }); }); } @@ -680,7 +674,7 @@ function run_test_27() { "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], callback_soon(check_test_27)); + ], check_test_27); AddonManagerPrivate.backgroundUpdateCheck(); } @@ -726,7 +720,7 @@ function run_test_28() { "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], callback_soon(check_test_28)); + ], check_test_28); AddonManagerPrivate.backgroundUpdateCheck(); } @@ -790,7 +784,7 @@ function check_test_29(install) { ["onUninstalled", false], ] }, [ - ], callback_soon(finish_test_29)); + ], finish_test_29); b2.uninstall(); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js index 867715863509..159ff7e58b62 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js @@ -32,7 +32,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_eq(a1, null); do_check_not_in_crash_annotation(addon1.id, addon1.version); @@ -57,7 +57,7 @@ function run_test() { run_test_1(); }); - })); + }); } // Disabling an add-on should work @@ -83,7 +83,7 @@ function run_test_1() { ensure_test_completed(); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, function(list) { do_check_eq(list.length, 1); do_check_eq(list[0].id, "addon1@tests.mozilla.org"); @@ -104,7 +104,7 @@ function run_test_1() { run_test_2(); }); - })); + }); }); } @@ -127,7 +127,7 @@ function run_test_2() { ensure_test_completed(); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, function(list) { do_check_eq(list.length, 1); do_check_eq(list[0].id, "addon1@tests.mozilla.org"); @@ -149,7 +149,7 @@ function run_test_2() { run_test_3(); }); - })); + }); }); } @@ -161,7 +161,7 @@ function run_test_3() { ] }); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { a1.userDisabled = true; ensure_test_completed(); prepare_test({ @@ -188,7 +188,7 @@ function run_test_3() { do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE)); - do_execute_soon(do_test_finished); + do_test_finished(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js index 9f5bfacca884..63089251e650 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js @@ -149,7 +149,7 @@ function run_test_4() { // Tests that after uninstalling a restart doesn't re-install the extension function run_test_5() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { a1.uninstall(); restartManager(); @@ -159,7 +159,7 @@ function run_test_5() { do_execute_soon(run_test_6); }); - })); + }); } // Tests that upgrading the application still doesn't re-install the uninstalled @@ -257,6 +257,6 @@ function run_test_9() { a2.uninstall(); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_dss.js b/toolkit/mozapps/extensions/test/xpcshell/test_dss.js index 7b171212a032..b096d55b63b1 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_dss.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dss.js @@ -115,7 +115,7 @@ function run_test() { } function end_test() { - do_execute_soon(do_test_finished); + do_test_finished(); } // Checks enabling one theme disables the others @@ -592,7 +592,7 @@ function run_test_9() { // Uninstalling a custom theme in use should require a restart function run_test_10() { - AddonManager.getAddonByID("theme2@tests.mozilla.org", callback_soon(function(oldt2) { + AddonManager.getAddonByID("theme2@tests.mozilla.org", function(oldt2) { prepare_test({ "theme2@tests.mozilla.org": [ ["onEnabling", false], @@ -611,8 +611,7 @@ function run_test_10() { restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([d, t2]) { + "theme2@tests.mozilla.org"], function([d, t2]) { do_check_true(t2.isActive); do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); @@ -639,8 +638,8 @@ function run_test_10() { restartManager(); do_execute_soon(run_test_11); - })); - })); + }); + }); } // Installing a custom theme not in use should not require a restart @@ -666,7 +665,7 @@ function run_test_11() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_11)); + ], check_test_11); install.install(); }); } @@ -723,7 +722,7 @@ function check_test_12() { // Updating a custom theme in use should require a restart function run_test_13() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { prepare_test({ "theme1@tests.mozilla.org": [ ["onEnabling", false], @@ -759,10 +758,10 @@ function run_test_13() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_13)); + ], check_test_13); install.install(); }); - })); + }); } function check_test_13() { @@ -773,6 +772,7 @@ function check_test_13() { do_check_true(t1.isActive); do_check_false(gLWThemeChanged); t1.uninstall(); + restartManager(); do_execute_soon(run_test_14); }); @@ -781,7 +781,6 @@ function check_test_13() { // Switching from a lightweight theme to the default theme should not require // a restart function run_test_14() { - restartManager(); LightweightThemeManager.currentTheme = { id: "1", version: "1", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js b/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js index 40b532c51ae9..748b8acd538a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js @@ -164,7 +164,7 @@ function run_test_2() { function run_test_3() { restartManager(); - AddonManager.getAddonByID(gPluginIDs[0], callback_soon(function(p) { + AddonManager.getAddonByID(gPluginIDs[0], function(p) { do_check_neq(p, null); do_check_eq(p.name, "Duplicate Plugin 1"); do_check_eq(p.description, "A duplicate plugin"); @@ -178,7 +178,7 @@ function run_test_3() { do_check_eq(p.name, "Duplicate Plugin 1"); do_check_eq(p.description, "A duplicate plugin"); - do_execute_soon(do_test_finished); + do_test_finished(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_error.js b/toolkit/mozapps/extensions/test/xpcshell/test_error.js index 2184399e2bd6..e01dcedb4525 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_error.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_error.js @@ -85,6 +85,6 @@ function run_test_5() { do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); do_check_eq(install.error, AddonManager.ERROR_CORRUPT_FILE); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js index cb661e495197..545a3df4f88f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js @@ -150,7 +150,7 @@ function run_test_2() { "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], callback_soon(check_test_2)); + ], check_test_2); install.install(); }, "application/x-xpinstall"); @@ -190,7 +190,7 @@ function run_test_3() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -203,7 +203,7 @@ function run_test_3() { do_check_true(source.exists()); do_execute_soon(run_test_4); - })); + }); } // Tests that misnaming a pointer doesn't clobber the sources @@ -238,7 +238,7 @@ function run_test_5() { restartManager(); - AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { + AddonManager.getAddonByID(addon1.id, function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -261,7 +261,7 @@ function run_test_5() { do_execute_soon(run_test_6); }); - })); + }); } // Removing the pointer file should uninstall the add-on @@ -273,7 +273,7 @@ function run_test_6() { restartManager(); - AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { + AddonManager.getAddonByID(addon1.id, function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -288,7 +288,7 @@ function run_test_6() { do_execute_soon(run_test_7); }); - })); + }); } // Removing the pointer file and replacing it with a directory should work @@ -297,7 +297,7 @@ function run_test_7() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -315,20 +315,20 @@ function run_test_7() { a1.uninstall(); + restartManager(); + do_execute_soon(run_test_8); }); - })); + }); } // Changes to the source files should be detected function run_test_8() { - restartManager(); - writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -342,21 +342,21 @@ function run_test_8() { a1.uninstall(); + restartManager(); + do_execute_soon(run_test_9); }); - })); + }); } // Removing the add-on the pointer file points at should uninstall the add-on function run_test_9() { - restartManager(); - var dest = writeInstallRDFForExtension(addon1, sourceDir); writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { + AddonManager.getAddonByID(addon1.id, function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); @@ -373,7 +373,7 @@ function run_test_9() { do_execute_soon(run_test_10); }); - })); + }); } // Tests that installing a new add-on by pointer with a relative path works diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js b/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js index 800933220e3d..bdd3f7e4f96c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js @@ -160,5 +160,5 @@ function onPrefChange(evt) { function onPrefChange2(evt) { do_check_eq(evt.data, testdata.dummy); - do_execute_soon(do_test_finished); + do_test_finished(); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_general.js b/toolkit/mozapps/extensions/test/xpcshell/test_general.js index e69b13314022..97b4971d2d89 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_general.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_general.js @@ -18,7 +18,7 @@ function run_test() { AddonManager.getAddonsByTypes(null, function(list) { gCount = list.length; - do_execute_soon(run_test_1); + run_test_1(); }); } @@ -51,8 +51,9 @@ function run_test_2() { function run_test_3() { restartManager(); - AddonManager.getAddonsByTypes(null, callback_soon(function(addons) { + AddonManager.getAddonsByTypes(null, function(addons) { do_check_eq(gCount, addons.length); + shutdownManager(); do_test_finished(); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js b/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js index 4dce15aec679..33c72182dbd6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js @@ -75,8 +75,7 @@ function run_test() { do_check_true(false); } - AddonManager.getInstallForFile(do_get_addon("test_getresource"), - callback_soon(function(aInstall) { + AddonManager.getInstallForFile(do_get_addon("test_getresource"), function(aInstall) { do_check_false(a1.hasResource("icon.png")); do_check_true(aInstall.addon.hasResource("icon.png")); @@ -85,9 +84,9 @@ function run_test() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) { do_check_eq(newa1, null); - do_execute_soon(do_test_finished); + do_test_finished(); }); - })); + }); }); }); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js index 598e06ed09bf..2390894d35d8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js @@ -76,7 +76,7 @@ function run_test() { do_check_true(a5.isActive); do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - do_execute_soon(do_test_finished); + do_test_finished(); }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js b/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js index ab96d1fdaffc..4e1e4f88a0a4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js @@ -50,7 +50,7 @@ function run_test_1() { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_1)); + ], check_test_1); // Fake a timer event gInternalManager.notify(null); @@ -105,7 +105,7 @@ function run_test_3() { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_3)); + ], check_test_3); // Fake a timer event gInternalManager.notify(null); @@ -189,7 +189,7 @@ function run_test_6() { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_6)); + ], check_test_6); // Fake a timer event gInternalManager.notify(null); @@ -261,7 +261,7 @@ function check_test_7(aInstall) { "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], callback_soon(finish_test_7)); + ], finish_test_7); // Fake a timer event gInternalManager.notify(null); @@ -326,13 +326,13 @@ function check_test_8() { } function finish_test_8() { - AddonManager.getAllInstalls(callback_soon(function(aInstalls) { + AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0].version, "2.0"); restartManager(); - AddonManager.getAddonByID("hotfix@tests.mozilla.org", callback_soon(function(aAddon) { + AddonManager.getAddonByID("hotfix@tests.mozilla.org", function(aAddon) { do_check_neq(aAddon, null); do_check_eq(aAddon.version, "2.0"); @@ -340,6 +340,6 @@ function finish_test_8() { restartManager(); end_test(); - })); - })); + }); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_install.js index cc85c053b9ae..2b014b8ef158 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js @@ -124,7 +124,7 @@ function check_test_1(installSyncGUID) { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) { + AddonManager.getAddonsWithOperationsByTypes(null, function(pendingAddons) { do_check_eq(pendingAddons.length, 1); do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org"); let uri = NetUtil.newURI(pendingAddons[0].iconURL); @@ -162,7 +162,7 @@ function check_test_1(installSyncGUID) { AddonManager.getAllInstalls(function(activeInstalls) { do_check_eq(activeInstalls, 0); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_neq(a1.syncGUID, null); do_check_true(a1.syncGUID.length >= 9); @@ -198,10 +198,10 @@ function check_test_1(installSyncGUID) { restartManager(); do_check_not_in_crash_annotation(a1.id, a1.version); - do_execute_soon(run_test_2); - })); + run_test_2(); + }); }); - })); + }); }); } @@ -275,7 +275,7 @@ function check_test_3(aInstall) { setExtensionModifiedTime(ext, updateDate); ensure_test_completed(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { do_check_eq(olda2, null); restartManager(); @@ -307,7 +307,7 @@ function check_test_3(aInstall) { run_test_4(); }); }); - })); + }); } // Tests that installing a new version of an existing add-on works @@ -376,7 +376,7 @@ function check_test_5(install) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); - AddonManager.getInstallsByTypes(null, callback_soon(function(installs) { + AddonManager.getInstallsByTypes(null, function(installs) { do_check_eq(installs.length, 1); do_check_eq(installs[0].addon, olda2.pendingUpgrade); restartManager(); @@ -402,17 +402,17 @@ function check_test_5(install) { do_check_true(a2.installDate <= a2.updateDate); a2.uninstall(); - do_execute_soon(run_test_6); + restartManager(); + + run_test_6(); }); }); - })); + }); }); } // Tests that an install that requires a compatibility update works function run_test_6() { - restartManager(); - prepare_test({ }, [ "onNewInstall" ]); @@ -464,7 +464,7 @@ function run_test_7() { function check_test_7() { ensure_test_completed(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(olda3) { do_check_eq(olda3, null); restartManager(); @@ -482,15 +482,15 @@ function check_test_7() { do_check_true(isExtensionInAddonsList(profileDir, a3.id)); do_check_true(do_get_addon("test_install3").exists()); a3.uninstall(); - do_execute_soon(run_test_8); + restartManager(); + + run_test_8(); }); }); - })); + }); } function run_test_8() { - restartManager(); - AddonManager.addInstallListener(InstallListener); AddonManager.addAddonListener(AddonListener); @@ -508,7 +508,7 @@ function run_test_8() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_8)); + ], check_test_8); install.install(); }); } @@ -527,14 +527,14 @@ function check_test_8() { do_check_true(isExtensionInAddonsList(profileDir, a3.id)); do_check_true(do_get_addon("test_install3").exists()); a3.uninstall(); - do_execute_soon(run_test_9); + restartManager(); + + run_test_9(); }); } // Test that after cancelling a download it is removed from the active installs function run_test_9() { - restartManager(); - prepare_test({ }, [ "onNewInstall" ]); @@ -625,7 +625,7 @@ function check_test_10(install) { ensure_test_completed(); - AddonManager.getAllInstalls(callback_soon(function(activeInstalls) { + AddonManager.getAllInstalls(function(activeInstalls) { do_check_eq(activeInstalls.length, 0); restartManager(); @@ -634,9 +634,9 @@ function check_test_10(install) { AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { do_check_eq(a3, null); - do_execute_soon(run_test_11); + run_test_11(); }); - })); + }); } // Tests that a multi-package install shows up as multiple installs with the @@ -738,7 +738,7 @@ function run_test_11() { "onInstallStarted", "onInstallEnded" ] - }, callback_soon(check_test_11)); + }, check_test_11); installs[0].install(); installs[1].install(); @@ -771,14 +771,14 @@ function check_test_11() { a6.uninstall(); a7.uninstall(); - do_execute_soon(run_test_12); + restartManager(); + + run_test_12(); }); } // Same as test 11 but for a remote XPI function run_test_12() { - restartManager(); - prepare_test({ }, [ "onNewInstall", ]); @@ -829,7 +829,7 @@ function run_test_12() { "onInstallStarted", "onInstallEnded" ] - }, callback_soon(check_test_12)); + }, check_test_12); install.install(); }, "application/x-xpinstall", null, "Multi Test 4"); } @@ -896,7 +896,9 @@ function check_test_12() { a6.uninstall(); a7.uninstall(); - do_execute_soon(run_test_13); + restartManager(); + + run_test_13(); }); } @@ -904,8 +906,6 @@ function check_test_12() { // Tests that cancelling an upgrade leaves the original add-on's pendingOperations // correct function run_test_13() { - restartManager(); - installAllFiles([do_get_addon("test_install2_1")], function() { restartManager(); @@ -953,7 +953,7 @@ function check_test_13(install) { do_check_eq(install.existingAddon.id, "addon2@tests.mozilla.org"); do_check_eq(install.addon.install, install); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); do_check_eq(olda2.pendingUpgrade, install.addon); @@ -984,15 +984,15 @@ function check_test_13(install) { a2.uninstall(); - do_execute_soon(run_test_14); + restartManager(); + + run_test_14(); }); - })); + }); } // Check that cancelling the install from onDownloadStarted actually cancels it function run_test_14() { - restartManager(); - prepare_test({ }, [ "onNewInstall" ]); @@ -1347,7 +1347,7 @@ function run_test_21() { } function check_test_21(aInstall) { - AddonManager.getAllInstalls(callback_soon(function(aInstalls) { + AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], aInstall); @@ -1370,7 +1370,7 @@ function check_test_21(aInstall) { run_test_22(); }); - })); + }); } // Tests that an install can be restarted after being cancelled diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js index 70f91c56013e..f4e192f91396 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js @@ -56,6 +56,6 @@ function test_4() { do_check_neq(aInstall.icons, null); do_check_eq(aInstall.icons[32], icon32_url); do_check_eq(aInstall.icons[64], icon64_url); - do_execute_soon(do_test_finished); + do_test_finished(); }, "application/x-xpinstall", null, null, { "32": icon32_url, "64": icon64_url }, null, null); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js index bcebc74c83c2..c5b99d5735d1 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js @@ -121,7 +121,7 @@ function check_test_1() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); - AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) { + AddonManager.getAddonsWithOperationsByTypes(null, function(pendingAddons) { do_check_eq(pendingAddons.length, 1); do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org"); let uri = NetUtil.newURI(pendingAddons[0].iconURL); @@ -191,7 +191,7 @@ function check_test_1() { do_execute_soon(function(){run_test_2(a1)}); }); }); - })); + }); }); } @@ -268,7 +268,7 @@ function check_test_3(aInstall) { setExtensionModifiedTime(ext, updateDate); ensure_test_completed(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { do_check_eq(olda2, null); restartManager(); @@ -299,7 +299,7 @@ function check_test_3(aInstall) { run_test_4(); }); }); - })); + }); } // Tests that installing a new version of an existing add-on works @@ -368,7 +368,7 @@ function check_test_5(install) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); - AddonManager.getInstallsByTypes(null, callback_soon(function(installs) { + AddonManager.getInstallsByTypes(null, function(installs) { do_check_eq(installs.length, 1); do_check_eq(installs[0].addon, olda2.pendingUpgrade); restartManager(); @@ -396,7 +396,7 @@ function check_test_5(install) { do_execute_soon(run_test_6); }); }); - })); + }); }); } @@ -455,7 +455,7 @@ function run_test_7() { function check_test_7() { ensure_test_completed(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", callback_soon(function(olda3) { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(olda3) { do_check_eq(olda3, null); restartManager(); @@ -475,7 +475,7 @@ function check_test_7() { do_execute_soon(run_test_8); }); }); - })); + }); } function run_test_8() { @@ -498,7 +498,7 @@ function run_test_8() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_8)); + ], check_test_8); install.install(); }); } @@ -614,7 +614,7 @@ function check_test_10(install) { ensure_test_completed(); - AddonManager.getAllInstalls(callback_soon(function(activeInstalls) { + AddonManager.getAllInstalls(function(activeInstalls) { do_check_eq(activeInstalls.length, 0); restartManager(); @@ -625,7 +625,7 @@ function check_test_10(install) { run_test_11(); }); - })); + }); } // Tests that a multi-package install shows up as multiple installs with the @@ -728,7 +728,7 @@ function run_test_11() { "onInstallStarted", "onInstallEnded" ] - }, callback_soon(check_test_11)); + }, check_test_11); installs[0].install(); installs[1].install(); @@ -820,7 +820,7 @@ function run_test_12() { "onInstallStarted", "onInstallEnded" ] - }, callback_soon(check_test_12)); + }, check_test_12); install.install(); }, "application/x-xpinstall", null, "Multi Test 4"); } @@ -944,7 +944,7 @@ function check_test_13(install) { do_check_eq(install.existingAddon.id, "addon2@tests.mozilla.org"); do_check_eq(install.addon.install, install); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { do_check_neq(olda2, null); do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE)); do_check_eq(olda2.pendingUpgrade, install.addon); @@ -977,7 +977,7 @@ function check_test_13(install) { do_execute_soon(run_test_14); }); - })); + }); } // Check that cancelling the install from onDownloadStarted actually cancels it @@ -1338,7 +1338,7 @@ function run_test_21() { } function check_test_21(aInstall) { - AddonManager.getAllInstalls(callback_soon(function(aInstalls) { + AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], aInstall); @@ -1361,7 +1361,7 @@ function check_test_21(aInstall) { run_test_22(); }); - })); + }); } // Tests that an install can be restarted after being cancelled @@ -1640,5 +1640,5 @@ function finish_test_27(aInstall) { ensure_test_completed(); - end_test(); + do_test_finished(); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js b/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js index 56edb06277c6..771f452e24a7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_langpack.js @@ -142,7 +142,7 @@ function run_test_2() { do_check_true(newb1.userDisabled); do_check_false(newb1.isActive); - do_execute_soon(run_test_3); + run_test_3(); }); }); } @@ -195,7 +195,7 @@ function run_test_4() { do_check_false(newb1.userDisabled); do_check_true(newb1.isActive); - do_execute_soon(run_test_5); + run_test_5(); }); }); } @@ -244,8 +244,7 @@ function check_test_7() { // check chrome reg that language pack is not registered do_check_locale_not_registered("test-langpack"); - AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(b1) { do_check_eq(b1, null); restartManager(); @@ -253,9 +252,9 @@ function check_test_7() { AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(newb1) { do_check_eq(newb1, null); - do_execute_soon(run_test_8); + run_test_8(); }); - })); + }); } // Tests that a locale detected in the profile starts working immediately @@ -266,8 +265,7 @@ function run_test_8() { startupManager(false); - AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", - callback_soon(function(b1) { + AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_false(b1.appDisabled); @@ -295,9 +293,9 @@ function run_test_8() { b2.uninstall(); ensure_test_completed(); - do_execute_soon(run_test_9); + run_test_9(); }); - })); + }); } // Tests that a locale from distribution/extensions gets installed and starts @@ -308,7 +306,7 @@ function run_test_9() { gAppInfo.version = "2.0"; startupManager(true); - AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", callback_soon(function(b1) { + AddonManager.getAddonByID("langpack-x-testing@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_false(b1.appDisabled); @@ -327,5 +325,5 @@ function run_test_9() { do_check_eq(chrome.getSelectedLocale("test-langpack"), "x-testing"); do_test_finished(); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locale.js b/toolkit/mozapps/extensions/test/xpcshell/test_locale.js index b4c7311e5391..288c6b2f2699 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locale.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locale.js @@ -34,7 +34,7 @@ function run_test_1() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(run_test_2)); + ], run_test_2); install.install(); }); } @@ -50,19 +50,19 @@ function run_test_2() { do_check_eq(addon.description, "fr-FR Description"); addon.userDisabled = true; - do_execute_soon(run_test_3); + restartManager(); + + run_test_3(); }); } // Test that the localized properties are still there when disabled. function run_test_3() { - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "fr-FR Name"); - do_execute_soon(run_test_4); + run_test_4(); }); } @@ -82,7 +82,7 @@ function run_test_4() { do_check_eq(contributors[1], "Fr Contributor 2"); do_check_eq(contributors[2], "Fr Contributor 3"); - do_execute_soon(run_test_5); + run_test_5(); }); } @@ -97,7 +97,7 @@ function run_test_5() { do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - do_execute_soon(run_test_6); + run_test_6(); }); } @@ -106,27 +106,26 @@ function run_test_6() { Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "nl-NL"); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(addon) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "Fallback Name"); do_check_eq(addon.description, "Fallback Description"); addon.userDisabled = false; - do_execute_soon(run_test_7); - })); + restartManager(); + run_test_7(); + }); } // Test that the prefs will override the fallbacks function run_test_7() { - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "Name from prefs"); - do_execute_soon(run_test_8); + run_test_8(); }); } @@ -144,6 +143,6 @@ function run_test_8() { do_check_eq(contributors[0], "Contributor 1"); do_check_eq(contributors[1], "Contributor 2"); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js old mode 100755 new mode 100644 index 90c86ceeddfd..91d09c07c7e2 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -4,9 +4,8 @@ // Checks that we rebuild something sensible from a corrupt database -Components.utils.import("resource://testing-common/httpd.js"); -Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://testing-common/httpd.js"); var testserver = new HttpServer(); testserver.start(-1); gPort = testserver.identity.primaryPort; @@ -255,28 +254,188 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // Open another handle on the JSON DB with as much Unix and Windows locking - // as we can to simulate some other process interfering with it + // After shutting down the database won't be open so we can + // mess with permissions shutdownManager(); - do_print("Locking " + gExtensionsJSON.path); - let options = { - winShare: 0 - }; - if (OS.Constants.libc.O_EXLOCK) - options.unixFlags = OS.Constants.libc.O_EXLOCK; + var savedPermissions = gExtensionsJSON.permissions; + gExtensionsJSON.permissions = 0; + + startupManager(false); + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + // Accessing the add-ons should open and recover the database + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function get_after_lock([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + // Should be correctly recovered + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + // Should be correctly recovered + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + // The compatibility update won't be recovered but it should still be + // active for this session + do_check_neq(a3, null); + do_check_true(a3.isActive); + do_check_false(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + + // The compatibility update won't be recovered and with strict + // compatibility it would not have been able to tell that it was + // previously userDisabled. However, without strict compat, it wasn't + // appDisabled, so it knows it must have been userDisabled. + do_check_neq(a4, null); + do_check_false(a4.isActive); + do_check_true(a4.userDisabled); + do_check_false(a4.appDisabled); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + + do_check_neq(a5, null); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + // Should be correctly recovered + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + // Should be correctly recovered + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + // Restarting will actually apply changes to extensions.ini which will + // then be put into the in-memory database when we next fail to load the + // real thing + restartManager(); + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + do_check_neq(a3, null); + do_check_true(a3.isActive); + do_check_false(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + + do_check_neq(a4, null); + do_check_false(a4.isActive); + do_check_true(a4.userDisabled); + do_check_false(a4.appDisabled); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + + do_check_neq(a5, null); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + // After allowing access to the original DB things should go back to as + // they were previously + shutdownManager(); + gExtensionsJSON.permissions = savedPermissions; + startupManager(); - OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then( - file => { - filePermissions = gExtensionsJSON.permissions; - if (!OS.Constants.Win) { - gExtensionsJSON.permissions = 0; - } - startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - // Accessing the add-ons should open and recover the database AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", @@ -286,8 +445,7 @@ function run_test_1() { "addon7@tests.mozilla.org", "theme1@tests.mozilla.org", "theme2@tests.mozilla.org"], - callback_soon(function get_after_lock([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - // Should be correctly recovered + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { do_check_neq(a1, null); do_check_true(a1.isActive); do_check_false(a1.userDisabled); @@ -295,7 +453,6 @@ function run_test_1() { do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - // Should be correctly recovered do_check_neq(a2, null); do_check_false(a2.isActive); do_check_true(a2.userDisabled); @@ -303,8 +460,6 @@ function run_test_1() { do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - // The compatibility update won't be recovered but it should still be - // active for this session do_check_neq(a3, null); do_check_true(a3.isActive); do_check_false(a3.userDisabled); @@ -312,10 +467,6 @@ function run_test_1() { do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - // The compatibility update won't be recovered and with strict - // compatibility it would not have been able to tell that it was - // previously userDisabled. However, without strict compat, it wasn't - // appDisabled, so it knows it must have been userDisabled. do_check_neq(a4, null); do_check_false(a4.isActive); do_check_true(a4.userDisabled); @@ -342,7 +493,6 @@ function run_test_1() { do_check_false(a7.appDisabled); do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - // Should be correctly recovered do_check_neq(t1, null); do_check_false(t1.isActive); do_check_true(t1.userDisabled); @@ -350,7 +500,6 @@ function run_test_1() { do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isThemeInAddonsList(profileDir, t1.id)); - // Should be correctly recovered do_check_neq(t2, null); do_check_true(t2.isActive); do_check_false(t2.userDisabled); @@ -358,174 +507,9 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // Restarting will actually apply changes to extensions.ini which will - // then be put into the in-memory database when we next fail to load the - // real thing - restartManager(); - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - do_check_neq(a3, null); - do_check_true(a3.isActive); - do_check_false(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - - do_check_neq(a4, null); - do_check_false(a4.isActive); - do_check_true(a4.userDisabled); - do_check_false(a4.appDisabled); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - - do_check_neq(a5, null); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - // After allowing access to the original DB things should go back to as - // they were previously - shutdownManager(); - do_print("Unlocking " + gExtensionsJSON.path); - file.close(); - gExtensionsJSON.permissions = filePermissions; - startupManager(); - - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - do_check_neq(a3, null); - do_check_true(a3.isActive); - do_check_false(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - - do_check_neq(a4, null); - do_check_false(a4.isActive); - do_check_true(a4.userDisabled); - do_check_false(a4.appDisabled); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - - do_check_neq(a5, null); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - end_test(); - })); - })); + end_test(); })); - }, - do_report_unexpected_exception - ); + })); + })); })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 1f97334fd37c..79c3439fd4e8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -5,8 +5,6 @@ // Checks that we handle a locked database when there are extension changes // in progress -Components.utils.import("resource://gre/modules/osfile.jsm"); - // Will be left alone var addon1 = { id: "addon1@tests.mozilla.org", @@ -91,7 +89,7 @@ function run_test() { check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { a2.userDisabled = true; restartManager(); @@ -144,152 +142,125 @@ function run_test() { do_check_eq(a5.pendingOperations, AddonManager.PENDING_UPGRADE); do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - // Open another handle on the JSON DB with as much Unix and Windows locking - // as we can to simulate some other process interfering with it + // After shutting down the database won't be open so we can lock it shutdownManager(); - do_print("Locking " + gExtensionsJSON.path); - let options = { - winShare: 0 - }; - if (OS.Constants.libc.O_EXLOCK) - options.unixFlags = OS.Constants.libc.O_EXLOCK; + var savedPermissions = gExtensionsJSON.permissions; + gExtensionsJSON.permissions = 0; - OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then( - file => { - filePermissions = gExtensionsJSON.permissions; - if (!OS.Constants.Win) { - gExtensionsJSON.permissions = 0; - } - startupManager(false); + startupManager(false); - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_false(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a2.id)); + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_false(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a2.id)); - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_true(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_true(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); - do_check_eq(a4, null); + do_check_eq(a4, null); - do_check_neq(a5, null); - do_check_eq(a5.version, "2.0"); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + do_check_neq(a5, null); + do_check_eq(a5.version, "2.0"); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a6.id)); + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - // After allowing access to the original DB things should still be - // back how they were before the lock - shutdownManager(); - file.close(); - gExtensionsJSON.permissions = filePermissions; - startupManager(); + // After allowing access to the original DB things should still be + // applied correctly + shutdownManager(); + gExtensionsJSON.permissions = savedPermissions; + startupManager(); - // On Unix, we can save the DB even when the original file wasn't - // readable, so our changes were saved. On Windows, - // these things happened when we had no access to the database so - // they are seen as external changes when we get the database back - if (gXPISaveError) { - do_print("Previous XPI save failed"); - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, - ["addon6@tests.mozilla.org"]); - check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, - ["addon4@tests.mozilla.org"]); - } - else { - do_print("Previous XPI save succeeded"); - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []); - } + // These things happened when we had no access to the database so + // they are seen as external changes when we get the database back :( + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, ["addon6@tests.mozilla.org"]); + check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, ["addon4@tests.mozilla.org"]); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_false(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a2.id)); + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_false(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a2.id)); - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_true(a3.userDisabled); - do_check_false(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_true(a3.userDisabled); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); - do_check_eq(a4, null); + do_check_eq(a4, null); - do_check_neq(a5, null); - do_check_eq(a5.version, "2.0"); - do_check_true(a5.isActive); - do_check_false(a5.userDisabled); - do_check_false(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a5.id)); + do_check_neq(a5, null); + do_check_eq(a5.version, "2.0"); + do_check_true(a5.isActive); + do_check_false(a5.userDisabled); + do_check_false(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a6.id)); + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - end_test(); - }); - })); - }, - do_report_unexpected_exception - ); + end_test(); + }); + }); }); }); - })); + }); } function end_test() { - do_execute_soon(do_test_finished); + do_test_finished(); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index 79f6b89be280..f38549cdaae2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -4,9 +4,8 @@ // Checks that we rebuild something sensible from a corrupt database -Components.utils.import("resource://testing-common/httpd.js"); -Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://testing-common/httpd.js"); var testserver = new HttpServer(); testserver.start(-1); gPort = testserver.identity.primaryPort; @@ -255,28 +254,184 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // Open another handle on the JSON DB with as much Unix and Windows locking - // as we can to simulate some other process interfering with it + // After shutting down the database won't be open so we can lock it shutdownManager(); - do_print("Locking " + gExtensionsJSON.path); - let options = { - winShare: 0 - }; - if (OS.Constants.libc.O_EXLOCK) - options.unixFlags = OS.Constants.libc.O_EXLOCK; + var savedPermissions = gExtensionsJSON.permissions; + gExtensionsJSON.permissions = 0; - OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then( - file => { - filePermissions = gExtensionsJSON.permissions; - if (!OS.Constants.Win) { - gExtensionsJSON.permissions = 0; - } + startupManager(false); + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + // Accessing the add-ons should open and recover the database + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + // Should be correctly recovered + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + // Should be correctly recovered + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + // The compatibility update won't be recovered but it should still be + // active for this session + do_check_neq(a3, null); + do_check_true(a3.isActive); + do_check_false(a3.userDisabled); + do_check_true(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE); + do_check_true(isExtensionInAddonsList(profileDir, a3.id)); + + // The compatibility update won't be recovered and it will not have been + // able to tell that it was previously userDisabled + do_check_neq(a4, null); + do_check_false(a4.isActive); + do_check_false(a4.userDisabled); + do_check_true(a4.appDisabled); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + + do_check_neq(a5, null); + do_check_false(a5.isActive); + do_check_false(a5.userDisabled); + do_check_true(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + // Should be correctly recovered + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + // Should be correctly recovered + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + // Restarting will actually apply changes to extensions.ini which will + // then be put into the in-memory database when we next fail to load the + // real thing + restartManager(); + + // Shouldn't have seen any startup changes + check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme1@tests.mozilla.org", + "theme2@tests.mozilla.org"], + callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_false(a1.appDisabled); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_true(a2.userDisabled); + do_check_false(a2.appDisabled); + do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a2.id)); + + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.userDisabled); + do_check_true(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a3.id)); + + do_check_neq(a4, null); + do_check_false(a4.isActive); + do_check_false(a4.userDisabled); + do_check_true(a4.appDisabled); + do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a4.id)); + + do_check_neq(a5, null); + do_check_false(a5.isActive); + do_check_false(a5.userDisabled); + do_check_true(a5.appDisabled); + do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isExtensionInAddonsList(profileDir, a5.id)); + + do_check_neq(a6, null); + do_check_true(a6.isActive); + do_check_false(a6.userDisabled); + do_check_false(a6.appDisabled); + do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_true(a7.userDisabled); + do_check_false(a7.appDisabled); + do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_false(t1.appDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(isThemeInAddonsList(profileDir, t1.id)); + + do_check_neq(t2, null); + do_check_true(t2.isActive); + do_check_false(t2.userDisabled); + do_check_false(t2.appDisabled); + do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(isThemeInAddonsList(profileDir, t2.id)); + + // After allowing access to the original DB things should go back to as + // they were previously + shutdownManager(); + gExtensionsJSON.permissions = savedPermissions; startupManager(false); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - // Accessing the add-ons should open and recover the database AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", @@ -287,7 +442,6 @@ function run_test_1() { "theme1@tests.mozilla.org", "theme2@tests.mozilla.org"], callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - // Should be correctly recovered do_check_neq(a1, null); do_check_true(a1.isActive); do_check_false(a1.userDisabled); @@ -295,7 +449,6 @@ function run_test_1() { do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - // Should be correctly recovered do_check_neq(a2, null); do_check_false(a2.isActive); do_check_true(a2.userDisabled); @@ -303,21 +456,17 @@ function run_test_1() { do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - // The compatibility update won't be recovered but it should still be - // active for this session do_check_neq(a3, null); do_check_true(a3.isActive); do_check_false(a3.userDisabled); - do_check_true(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE); + do_check_false(a3.appDisabled); + do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - // The compatibility update won't be recovered and it will not have been - // able to tell that it was previously userDisabled do_check_neq(a4, null); do_check_false(a4.isActive); - do_check_false(a4.userDisabled); - do_check_true(a4.appDisabled); + do_check_true(a4.userDisabled); + do_check_false(a4.appDisabled); do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isExtensionInAddonsList(profileDir, a4.id)); @@ -340,7 +489,6 @@ function run_test_1() { do_check_false(a7.appDisabled); do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - // Should be correctly recovered do_check_neq(t1, null); do_check_false(t1.isActive); do_check_true(t1.userDisabled); @@ -348,7 +496,6 @@ function run_test_1() { do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); do_check_false(isThemeInAddonsList(profileDir, t1.id)); - // Should be correctly recovered do_check_neq(t2, null); do_check_true(t2.isActive); do_check_false(t2.userDisabled); @@ -356,195 +503,9 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // Restarting will actually apply changes to extensions.ini which will - // then be put into the in-memory database when we next fail to load the - // real thing - restartManager(); - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.userDisabled); - do_check_true(a3.appDisabled); - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a3.id)); - - do_check_neq(a4, null); - do_check_false(a4.isActive); - do_check_false(a4.userDisabled); - do_check_true(a4.appDisabled); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - - do_check_neq(a5, null); - do_check_false(a5.isActive); - do_check_false(a5.userDisabled); - do_check_true(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - // After allowing access to the original DB things should go back to as - // back how they were before the lock - shutdownManager(); - do_print("Unlocking " + gExtensionsJSON.path); - file.close(); - gExtensionsJSON.permissions = filePermissions; - startupManager(false); - - // Shouldn't have seen any startup changes - check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) { - do_check_neq(a1, null); - do_check_true(a1.isActive); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a2.id)); - - do_check_neq(a3, null); - do_check_false(a3.userDisabled); - // On Unix, we may be able to save our changes over the locked DB so we - // remember that this extension was changed to disabled. On Windows we - // couldn't replace the old DB so we read the older version of the DB - // where the extension is enabled - if (gXPISaveError) { - do_print("XPI save failed"); - do_check_true(a3.isActive); - do_check_false(a3.appDisabled); - do_check_true(isExtensionInAddonsList(profileDir, a3.id)); - } - else { - do_print("XPI save succeeded"); - do_check_false(a3.isActive); - do_check_true(a3.appDisabled); - do_check_false(isExtensionInAddonsList(profileDir, a3.id)); - } - do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a4, null); - do_check_false(a4.isActive); - // The reverse of the platform difference for a3 - Unix should - // stay the same as the last iteration because the save succeeded, - // Windows should still say userDisabled - if (OS.Constants.Win) { - do_check_true(a4.userDisabled); - do_check_false(a4.appDisabled); - } - else { - do_check_false(a4.userDisabled); - do_check_true(a4.appDisabled); - } - do_check_false(isExtensionInAddonsList(profileDir, a4.id)); - do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a5, null); - do_check_false(a5.isActive); - do_check_false(a5.userDisabled); - do_check_true(a5.appDisabled); - do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isExtensionInAddonsList(profileDir, a5.id)); - - do_check_neq(a6, null); - do_check_true(a6.isActive); - do_check_false(a6.userDisabled); - do_check_false(a6.appDisabled); - do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_true(a7.userDisabled); - do_check_false(a7.appDisabled); - do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - - do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - - end_test(); - })); - })); + end_test(); })); - }, - do_report_unexpected_exception - ); + })); + })); })); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js b/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js index 061a3a6b2b85..e228dc61305f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_manifest.js @@ -557,6 +557,6 @@ function run_test() { do_check_eq(a26.optionsType, AddonManager.OPTIONS_TYPE_INLINE_INFO); do_check_neq(a26.optionsURL, null); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js b/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js index 6a116746549b..a01701c57bc3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js @@ -140,7 +140,7 @@ function run_test_2(uri) { do_check_true(newb1.userDisabled); check_mapping(uri, newb1.id); - do_execute_soon(() => run_test_3(uri)); + run_test_3(uri); }); }); } @@ -174,7 +174,7 @@ function run_test_4() { let uri = newb1.getResourceURI("."); check_mapping(uri, newb1.id); - do_execute_soon(run_test_5); + run_test_5(); }); }); } @@ -187,7 +187,7 @@ function run_test_5() { let uri = b1.getResourceURI("."); check_mapping(uri, b1.id); - do_execute_soon(run_test_invalidarg); + run_test_invalidarg(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js index ffef6b7d169b..478e697cb78f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js @@ -246,6 +246,6 @@ function run_test() { do_check_false(stagedXPIs.exists()); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js index c213bace73fa..c703b25c19a9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -254,6 +254,6 @@ function run_test() { do_check_false(a8.strictCompatibility); do_check_true(a8.foreignInstall); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js index f27d53c3fc9f..ef7bdd3f603c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js @@ -234,6 +234,6 @@ function run_test() { do_check_false(stagedXPIs.exists()); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js index 0109dcf927a6..91a05fa358e3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js @@ -134,6 +134,6 @@ function run_test() { do_check_false(d.appDisabled); do_check_false(d.isActive); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js b/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js index f9b7da073cd6..d191ed33bdde 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js @@ -59,7 +59,7 @@ function run_test_2() { "addon1@tests.mozilla.org": [ ["onPropertyChanged", ["appDisabled"]] ] - }, [], callback_soon(do_test_finished)); + }, [], do_test_finished); AddonManager.strictCompatibility = false; }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js index ba24e92e019c..917e2aa7d4d3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js @@ -75,7 +75,7 @@ function run_test() { } function end_test() { - do_execute_soon(do_test_finished); + do_test_finished(); } function sortAddons(addons) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js b/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js index 7051ca14470b..3de712cdae40 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_plugins.js @@ -173,6 +173,6 @@ function run_test_4() { Services.prefs.clearUserPref("plugins.click_to_play"); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_registry.js b/toolkit/mozapps/extensions/test/xpcshell/test_registry.js index 010250457986..a2d8b80a31d0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_registry.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_registry.js @@ -146,6 +146,6 @@ function run_test_4() { do_check_eq(a1, null); do_check_neq(a2, null); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js b/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js index 05647f807f3b..dbbe848fc5db 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_safemode.js @@ -33,7 +33,7 @@ function run_test() { startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_eq(a1, null); do_check_not_in_crash_annotation(addon1.id, addon1.version); @@ -57,7 +57,7 @@ function run_test() { run_test_1(); }); - })); + }); } // Disabling an add-on should work @@ -110,6 +110,6 @@ function run_test_2() { ensure_test_completed(); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index 36e3da0465a3..a14f07e43471 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -828,7 +828,7 @@ function run_test_12() { "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org"], - callback_soon(function([a1, a2, a3, a4, a5]) { + function([a1, a2, a3, a4, a5]) { do_check_neq(a1, null); do_check_false(a1.userDisabled); do_check_true(a1.isActive); @@ -917,8 +917,8 @@ function run_test_12() { do_check_true(a3.userDisabled); do_check_false(a3.isActive); - do_execute_soon(end_test); + end_test(); }); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js index f57fa4d22d3b..c73a412c4870 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js @@ -86,7 +86,7 @@ add_test(function test_error_on_duplicate_syncguid_insert() { do_execute_soon(function duplicate_syncguid_install_ended() { restartManager(); - AddonManager.getAddonsByIDs(installIDs, callback_soon(function(addons) { + AddonManager.getAddonsByIDs(installIDs, function(addons) { let initialGUID = addons[1].syncGUID; try { @@ -102,7 +102,7 @@ add_test(function test_error_on_duplicate_syncguid_insert() { run_next_test(); }); } - })); + }); }); } } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js b/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js index ef4f2aee562e..011288e814a6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js @@ -141,6 +141,6 @@ function run_test() { do_check_false(isExtensionInAddonsList(profileDir, a5.id)); do_check_not_in_crash_annotation(addon5.id, addon5.version); - do_execute_soon(do_test_finished); + do_test_finished(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js index f201c776d879..6b2e0c94da3c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js @@ -126,7 +126,7 @@ function run_test() { } function end_test() { - do_execute_soon(do_test_finished); + do_test_finished(); } // Checks enabling one theme disables the others @@ -613,7 +613,7 @@ function run_test_9() { // Uninstalling a custom theme in use should require a restart function run_test_10() { - AddonManager.getAddonByID("theme2@tests.mozilla.org", callback_soon(function(oldt2) { + AddonManager.getAddonByID("theme2@tests.mozilla.org", function(oldt2) { prepare_test({ "theme2@tests.mozilla.org": [ "onEnabling", @@ -654,7 +654,7 @@ function run_test_10() { do_execute_soon(run_test_11); }); - })); + }); } // Installing a custom theme not in use should not require a restart @@ -742,7 +742,7 @@ function check_test_12() { // Updating a custom theme in use should require a restart function run_test_13() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { prepare_test({ "theme1@tests.mozilla.org": [ "onEnabling", @@ -777,16 +777,16 @@ function run_test_13() { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_13)); + ], check_test_13); install.install(); }); - })); + }); } function check_test_13() { restartManager(); - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { do_check_neq(t1, null); do_check_true(t1.isActive); do_check_false(gLWThemeChanged); @@ -794,7 +794,7 @@ function check_test_13() { restartManager(); do_execute_soon(run_test_14); - })); + }); } // Switching from a lightweight theme to the default theme should not require @@ -846,15 +846,14 @@ function run_test_15() { restartManager(); installAllFiles([do_get_addon("test_theme")], function() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { t1.userDisabled = false; restartManager(); do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1/1.0"); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], - callback_soon(function([d, t1]) { + "theme1@tests.mozilla.org"], function([d, t1]) { do_check_true(d.userDisabled); do_check_false(d.appDisabled); do_check_false(d.isActive); @@ -878,8 +877,8 @@ function run_test_15() { do_execute_soon(run_test_16); }); - })); - })); + }); + }); }); } @@ -933,14 +932,13 @@ function run_test_17() { function run_test_18() { restartManager(2); - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { t1.userDisabled = false; restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], - callback_soon(function([d, t1]) { + "theme1@tests.mozilla.org"], function([d, t1]) { do_check_true(d.userDisabled); do_check_false(d.appDisabled); do_check_false(d.isActive); @@ -982,8 +980,8 @@ function run_test_18() { do_execute_soon(run_test_19); }); - })); - })); + }); + }); } // Disabling the active persona should switch back to the default theme @@ -1046,7 +1044,7 @@ function run_test_20() { // Tests that cached copies of a lightweight theme have the right permissions // and pendingOperations during the onEnabling event function run_test_21() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { // Switch to a custom theme so we can test pendingOperations properly. prepare_test({ @@ -1088,5 +1086,5 @@ function run_test_21() { end_test(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js index 02d37d89b745..38349a22f542 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js @@ -25,7 +25,7 @@ function run_test() { do_test_pending(); startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); writeInstallRDFForExtension(addon1, profileDir); @@ -40,13 +40,13 @@ function run_test() { do_check_eq(a1.pendingOperations, 0); do_check_in_crash_annotation(addon1.id, addon1.version); - do_execute_soon(run_test_1); + run_test_1(); }); - })); + }); } function end_test() { - do_execute_soon(do_test_finished); + do_test_finished(); } // Uninstalling an add-on should work. diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index 793a8d545521..e222b8e126e2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -210,7 +210,7 @@ function run_test_2(install) { function check_test_2() { ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_neq(olda1, null); do_check_eq(olda1.version, "1.0"); do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); @@ -234,7 +234,7 @@ function check_test_2() { a1.uninstall(); do_execute_soon(run_test_3); }); - })); + }); } @@ -401,7 +401,7 @@ function continue_test_6(install) { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_6)); + ], check_test_6); } function check_test_6(install) { @@ -413,14 +413,14 @@ function check_test_6(install) { do_check_eq(a1.version, "2.0"); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); a1.uninstall(); - do_execute_soon(run_test_7); + restartManager(); + + run_test_7(); }); } // Test that background update checks work for lightweight themes function run_test_7() { - restartManager(); - LightweightThemeManager.currentTheme = { id: "1", version: "1", @@ -578,7 +578,7 @@ function check_test_7_cache() { do_check_eq(p1.installDate.getTime(), gInstallDate); do_check_true(p1.installDate.getTime() < p1.updateDate.getTime()); - do_execute_soon(run_test_8); + run_test_8(); }); } @@ -662,7 +662,7 @@ function run_test_8() { restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { a2.userDisabled = true; restartManager(); @@ -756,7 +756,7 @@ function run_test_8() { let compatListener = { onUpdateFinished: function(addon, error) { if (--count == 0) - do_execute_soon(run_next_test); + run_next_test(); } }; @@ -778,7 +778,7 @@ function run_test_8() { a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); - })); + }); } // Tests that if an install.rdf claims compatibility then the add-on will be @@ -828,7 +828,7 @@ function run_test_11() { onUpdateFinished: function(addon) { do_check_true(addon.isCompatible); - do_execute_soon(run_test_12); + run_test_12(); } }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); @@ -1296,7 +1296,7 @@ function continue_test_20(install) { }, [ "onInstallStarted", "onInstallEnded", - ], callback_soon(check_test_20)); + ], check_test_20); } function check_test_20(install) { @@ -1308,10 +1308,8 @@ function check_test_20(install) { do_check_eq(a12.version, "2.0"); do_check_eq(a12.type, "extension"); a12.uninstall(); + restartManager(); - do_execute_soon(() => { - restartManager(); - end_test(); - }); + end_test(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 1eba7779aa71..993f91f732a7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -207,7 +207,7 @@ function run_test_2(install) { function check_test_2() { ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_neq(olda1, null); do_check_eq(olda1.version, "1.0"); do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); @@ -228,7 +228,7 @@ function check_test_2() { a1.uninstall(); do_execute_soon(run_test_3); }); - })); + }); } @@ -496,7 +496,7 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - do_execute_soon(run_test_8); + run_test_8(); }); } @@ -580,7 +580,7 @@ function run_test_8() { restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { a2.userDisabled = true; restartManager(); @@ -696,7 +696,7 @@ function run_test_8() { a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); - })); + }); } // Tests that if an install.rdf claims compatibility then the add-on will be @@ -746,7 +746,7 @@ function run_test_11() { onUpdateFinished: function(addon) { do_check_false(addon.isCompatible); - do_execute_soon(run_test_12); + run_test_12(); } }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); }); @@ -1085,7 +1085,7 @@ function run_test_17() { }, onUpdateFinished: function() { - do_execute_soon(end_test); + end_test(); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js b/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js index e8aea0301697..069ed0671141 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updateid.js @@ -93,7 +93,7 @@ function run_test_1() { } function check_test_1(install) { - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { // Existing add-on should have a pending upgrade do_check_neq(a1.pendingUpgrade, null); do_check_eq(a1.pendingUpgrade.id, "addon2@tests.mozilla.org"); @@ -118,7 +118,7 @@ function check_test_1(install) { do_execute_soon(run_test_2); }); - })); + }); } // Test that when the new add-on already exists we just upgrade that @@ -164,8 +164,7 @@ function run_test_2() { function check_test_2(install) { AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org"], - callback_soon(function([a1, a2]) { + "addon2@tests.mozilla.org"], function([a1, a2]) { do_check_eq(a1.pendingUpgrade, null); // Existing add-on should have a pending upgrade do_check_neq(a2.pendingUpgrade, null); @@ -185,7 +184,7 @@ function check_test_2(install) { do_execute_soon(run_test_3); }); - })); + }); } // Test that we rollback correctly when removing the old add-on fails @@ -226,7 +225,7 @@ function run_test_3() { } function check_test_3(install) { - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { // Existing add-on should have a pending upgrade do_check_neq(a1.pendingUpgrade, null); do_check_eq(a1.pendingUpgrade.id, "addon2@tests.mozilla.org"); @@ -249,8 +248,7 @@ function check_test_3(install) { fstream.close(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org"], - callback_soon(function([a1, a2]) { + "addon2@tests.mozilla.org"], function([a1, a2]) { // Should not have installed the new add-on but it should still be // pending install do_check_neq(a1, null); @@ -268,8 +266,8 @@ function check_test_3(install) { do_execute_soon(run_test_4); }); - })); - })); + }); + }); } // Tests that upgrading to a bootstrapped add-on works but requires a restart @@ -308,8 +306,7 @@ function run_test_4() { function check_test_4() { AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", - "addon3@tests.mozilla.org"], - callback_soon(function([a2, a3]) { + "addon3@tests.mozilla.org"], function([a2, a3]) { // Should still be pending install even though the new add-on is restartless do_check_neq(a2, null); do_check_eq(a3, null); @@ -333,7 +330,7 @@ function check_test_4() { do_execute_soon(run_test_5); }); - })); + }); } // Tests that upgrading to another bootstrapped add-on works without a restart @@ -352,8 +349,7 @@ function run_test_5() { function check_test_5() { AddonManager.getAddonsByIDs(["addon3@tests.mozilla.org", - "addon4@tests.mozilla.org"], - callback_soon(function([a3, a4]) { + "addon4@tests.mozilla.org"], function([a3, a4]) { // Should have updated do_check_eq(a3, null); do_check_neq(a4, null); @@ -374,7 +370,7 @@ function check_test_5() { run_test_6(); }); - })); + }); } // Tests that upgrading to a non-bootstrapped add-on works but requires a restart @@ -393,8 +389,7 @@ function run_test_6() { function check_test_6() { AddonManager.getAddonsByIDs(["addon4@tests.mozilla.org", - "addon2@tests.mozilla.org"], - callback_soon(function([a4, a2]) { + "addon2@tests.mozilla.org"], function([a4, a2]) { // Should still be pending install even though the old add-on is restartless do_check_neq(a4, null); do_check_eq(a2, null); @@ -418,5 +413,5 @@ function check_test_6() { end_test(); }); - })); + }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js index 67bec392efd8..a5e2c532bed6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js @@ -93,7 +93,7 @@ function end_test() { globalDir.append(do_get_expected_addon_name("addon4@tests.mozilla.org")); globalDir.remove(true); } - do_execute_soon(do_test_finished); + do_test_finished(); } // Test that the test extensions are all installed diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js index 96e1cbd8d19b..22aa7114fd1b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js @@ -96,7 +96,7 @@ function end_test() { Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); - do_execute_soon(do_test_finished); + do_test_finished(); } // Test that the test extensions are all installed From 548be46eeb44b7290b91563e30865e1b98946fe5 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:20:03 +0200 Subject: [PATCH 26/40] Backed out changeset 40c48d65e382 (bug 853388) --- toolkit/mozapps/extensions/XPIProvider.jsm | 3 +- .../mozapps/extensions/XPIProviderUtils.js | 478 +++++++----------- .../test/xpcshell/test_migrate_max_version.js | 27 +- 3 files changed, 196 insertions(+), 312 deletions(-) diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 61ef77ed3fc7..83adbdc628b6 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -3218,8 +3218,9 @@ var XPIProvider = { // If the database needs to be updated then open it and then update it // from the filesystem if (updateDatabase || hasPendingChanges) { - XPIDatabase.syncLoadDB(false); try { + XPIDatabase.openConnection(false, true); + extensionListChanged = this.processFileChanges(state, manifests, aAppChanged, aOldAppVersion, diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 89ce9fac9c0d..9147cb1b448e 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -20,8 +20,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave", "resource://gre/modules/DeferredSave.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); ["LOG", "WARN", "ERROR"].forEach(function(aName) { Object.defineProperty(this, aName, { @@ -160,20 +158,6 @@ function getRepositoryAddon(aAddon, aCallback) { AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); } -/** - * Wrap an API-supplied function in an exception handler to make it safe to call - */ -function safeCallback(aCallback) { - return function(...aArgs) { - try { - aCallback.apply(null, aArgs); - } - catch(ex) { - WARN("XPI Database callback failed", ex); - } - } -} - /** * A helper method to asynchronously call a function on an array * of objects, calling a callback when function(x) has been gathered @@ -391,32 +375,6 @@ DBAddonInternal.prototype = { DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; -/** - * Internal interface: find an addon from an already loaded addonDB - */ -function _findAddon(addonDB, aFilter) { - for (let [, addon] of addonDB) { - if (aFilter(addon)) { - return addon; - } - } - return null; -} - -/** - * Internal interface to get a filtered list of addons from a loaded addonDB - */ -function _filterDB(addonDB, aFilter) { - let addonList = []; - for (let [, addon] of addonDB) { - if (aFilter(addon)) { - addonList.push(addon); - } - } - - return addonList; -} - this.XPIDatabase = { // true if the database connection has been opened initialized: false, @@ -452,12 +410,6 @@ this.XPIDatabase = { return; } - if (!this._deferredSave) { - this._deferredSave = new DeferredSave(this.jsonFile.path, - () => JSON.stringify(this), - ASYNC_SAVE_DELAY_MS); - } - let promise = this._deferredSave.saveChanges(); if (!this._schemaVersionSet) { this._schemaVersionSet = true; @@ -477,8 +429,8 @@ this.XPIDatabase = { }, flush: function() { - // handle the "in memory only" and "saveChanges never called" cases - if (!this._deferredSave) { + // handle the "in memory only" case + if (this.lockedDatabase) { let done = Promise.defer(); done.resolve(0); return done.promise; @@ -487,17 +439,19 @@ this.XPIDatabase = { return this._deferredSave.flush(); }, + get _deferredSave() { + delete this._deferredSave; + return this._deferredSave = + new DeferredSave(this.jsonFile.path, () => JSON.stringify(this), + ASYNC_SAVE_DELAY_MS); + }, + /** * Converts the current internal state of the XPI addon database to JSON */ toJSON: function() { - if (!this.addonDB) { - // We never loaded the database? - throw new Error("Attempt to save database without loading it first"); - } - let addons = []; - for (let [, addon] of this.addonDB) { + for (let [key, addon] of this.addonDB) { addons.push(addon); } let toSave = { @@ -535,7 +489,7 @@ this.XPIDatabase = { }, /** - * Synchronously opens and reads the database file, upgrading from old + * Opens and reads the database file, upgrading from old * databases or making a new DB if needed. * * The possibilities, in order of priority, are: @@ -552,8 +506,10 @@ this.XPIDatabase = { * from the install locations if the database needs to be rebuilt. * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ - syncLoadDB: function XPIDB_syncLoadDB(aRebuildOnError) { - // XXX TELEMETRY report synchronous opens (startup time) vs. delayed opens + openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) { + // XXX TELEMETRY report opens with aRebuildOnError true (which implies delayed open) + // vs. aRebuildOnError false (DB loaded during startup) + delete this.addonDB; this.migrateData = null; let fstream = null; let data = ""; @@ -574,10 +530,42 @@ this.XPIDatabase = { data += str.value; } while (read != 0); } - this.parseDB(data, aRebuildOnError); + // dump("Loaded JSON:\n" + data + "\n"); + let inputAddons = JSON.parse(data); + // Now do some sanity checks on our JSON db + if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { + // Content of JSON file is bad, need to rebuild from scratch + ERROR("bad JSON file contents"); + this.rebuildDatabase(aRebuildOnError); + } + if (inputAddons.schemaVersion != DB_SCHEMA) { + // Handle mismatched JSON schema version. For now, we assume + // compatibility for JSON data, though we throw away any fields we + // don't know about + // XXX preserve unknown fields during save/restore + LOG("JSON schema mismatch: expected " + DB_SCHEMA + + ", actual " + inputAddons.schemaVersion); + } + // If we got here, we probably have good data + // Make AddonInternal instances from the loaded data and save them + let addonDB = new Map(); + inputAddons.addons.forEach(function(loadedAddon) { + let newAddon = new DBAddonInternal(loadedAddon); + addonDB.set(newAddon._key, newAddon); + }); + this.addonDB = addonDB; + LOG("Successfully read XPI database"); + this.initialized = true; } catch(e) { - ERROR("Failed to load XPI JSON data from profile", e); + // If we catch and log a SyntaxError from the JSON + // parser, the xpcshell test harness fails the test for us: bug 870828 + if (e.name == "SyntaxError") { + ERROR("Syntax error parsing saved XPI JSON data"); + } + else { + ERROR("Failed to load XPI JSON data from profile", e); + } this.rebuildDatabase(aRebuildOnError); } finally { @@ -587,151 +575,46 @@ this.XPIDatabase = { } catch (e) { if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { - this.upgradeDB(aRebuildOnError); + try { + let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); + if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { + // we should have an older SQLITE database + this.migrateData = this.getMigrateDataFromSQLITE(); + } + // else we've upgraded before but the JSON file is gone, fall through + // and rebuild from scratch + } + catch(e) { + // No schema version pref means either a really old upgrade (RDF) or + // a new profile + this.migrateData = this.getMigrateDataFromRDF(); + } + + this.rebuildDatabase(aRebuildOnError); } else { - this.rebuildUnreadableDB(e, aRebuildOnError); + WARN("Extensions database " + this.jsonFile.path + + " exists but is not readable; rebuilding in memory", e); + // XXX open question - if we can overwrite at save time, should we, or should we + // leave the locked database in case we can recover from it next time we start up? + // The old code made one attempt to remove the locked file before it rebuilt in memory + this.lockedDatabase = true; + // XXX TELEMETRY report when this happens? + this.rebuildDatabase(aRebuildOnError); } } finally { if (fstream) fstream.close(); } - // If an async load was also in progress, resolve that promise with our DB; - // otherwise create a resolved promise - if (this._dbPromise) - this._dbPromise.resolve(this.addonDB); - else - this._dbPromise = Promise.resolve(this.addonDB); - }, - /** - * Parse loaded data, reconstructing the database if the loaded data is not valid - * @param aRebuildOnError - * If true, synchronously reconstruct the database from installed add-ons - */ - parseDB: function(aData, aRebuildOnError) { - try { - // dump("Loaded JSON:\n" + aData + "\n"); - let inputAddons = JSON.parse(aData); - // Now do some sanity checks on our JSON db - if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { - // Content of JSON file is bad, need to rebuild from scratch - ERROR("bad JSON file contents"); - this.rebuildDatabase(aRebuildOnError); - return; - } - if (inputAddons.schemaVersion != DB_SCHEMA) { - // Handle mismatched JSON schema version. For now, we assume - // compatibility for JSON data, though we throw away any fields we - // don't know about - // XXX preserve unknown fields during save/restore - LOG("JSON schema mismatch: expected " + DB_SCHEMA + - ", actual " + inputAddons.schemaVersion); - } - // If we got here, we probably have good data - // Make AddonInternal instances from the loaded data and save them - let addonDB = new Map(); - inputAddons.addons.forEach(function(loadedAddon) { - let newAddon = new DBAddonInternal(loadedAddon); - addonDB.set(newAddon._key, newAddon); - }); - this.addonDB = addonDB; - LOG("Successfully read XPI database"); - this.initialized = true; + return; + + // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"? + if (!aForceOpen && !this.dbfileExists) { + this.connection = null; + return; } - catch(e) { - // If we catch and log a SyntaxError from the JSON - // parser, the xpcshell test harness fails the test for us: bug 870828 - if (e.name == "SyntaxError") { - ERROR("Syntax error parsing saved XPI JSON data"); - } - else { - ERROR("Failed to load XPI JSON data from profile", e); - } - this.rebuildDatabase(aRebuildOnError); - } - }, - - /** - * Upgrade database from earlier (sqlite or RDF) version if available - */ - upgradeDB: function(aRebuildOnError) { - try { - let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); - if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { - // we should have an older SQLITE database - this.migrateData = this.getMigrateDataFromSQLITE(); - } - // else we've upgraded before but the JSON file is gone, fall through - // and rebuild from scratch - } - catch(e) { - // No schema version pref means either a really old upgrade (RDF) or - // a new profile - this.migrateData = this.getMigrateDataFromRDF(); - } - - this.rebuildDatabase(aRebuildOnError); - }, - - /** - * Reconstruct when the DB file exists but is unreadable - * (for example because read permission is denied - */ - rebuildUnreadableDB: function(aError, aRebuildOnError) { - WARN("Extensions database " + this.jsonFile.path + - " exists but is not readable; rebuilding in memory", aError); - // XXX open question - if we can overwrite at save time, should we, or should we - // leave the locked database in case we can recover from it next time we start up? - // The old code made one attempt to remove the locked file before it rebuilt in memory - this.lockedDatabase = true; - // XXX TELEMETRY report when this happens? - this.rebuildDatabase(aRebuildOnError); - }, - - /** - * Open and read the XPI database asynchronously, upgrading if - * necessary. If any DB load operation fails, we need to - * synchronously rebuild the DB from the installed extensions. - * - * @return Promise resolves to the Map of loaded JSON data stored - * in this.addonDB; never rejects. - */ - asyncLoadDB: function XPIDB_asyncLoadDB(aDBCallback) { - // Already started (and possibly finished) loading - if (this._dbPromise) { - return this._dbPromise; - } - - LOG("Starting async load of XPI database " + this.jsonFile.path); - return this._dbPromise = OS.File.read(this.jsonFile.path).then( - byteArray => { - if (this._addonDB) { - LOG("Synchronous load completed while waiting for async load"); - return this.addonDB; - } - LOG("Finished async read of XPI database, parsing..."); - let decoder = new TextDecoder(); - let data = decoder.decode(byteArray); - this.parseDB(data, true); - return this.addonDB; - }) - .then(null, - error => { - if (this._addonDB) { - LOG("Synchronous load completed while waiting for async load"); - return this.addonDB; - } - if (error.becauseNoSuchFile) { - this.upgradeDB(true); - } - else { - // it's there but unreadable - this.rebuildUnreadableDB(error, true); - } - return this.addonDB; - }); }, /** @@ -766,13 +649,21 @@ this.XPIDatabase = { } }, + /** + * Lazy getter for the addons database + */ + get addonDB() { + this.openConnection(true); + return this.addonDB; + }, + /** * Gets the list of file descriptors of active extension directories or XPI * files from the add-ons list. This must be loaded from disk since the * directory service gives no easy way to get both directly. This list doesn't * include themes as preferences already say which theme is currently active * - * @return an array of persistent descriptors for the directories + * @return an array of persisitent descriptors for the directories */ getActiveBundles: function XPIDB_getActiveBundles() { let bundles = []; @@ -991,11 +882,27 @@ this.XPIDatabase = { return 0; }) .then(count => { - // Clear out the cached addons data loaded from JSON + // Clear out the cached addons data loaded from JSON and recreate + // the getter to allow database re-loads during testing. delete this.addonDB; - delete this._dbPromise; + Object.defineProperty(this, "addonDB", { + get: function addonsGetter() { + this.openConnection(true); + return this.addonDB; + }, + configurable: true + }); // same for the deferred save delete this._deferredSave; + Object.defineProperty(this, "_deferredSave", { + set: function deferredSaveGetter() { + delete this._deferredSave; + return this._deferredSave = + new DeferredSave(this.jsonFile.path, this.formJSON.bind(this), + ASYNC_SAVE_DELAY_MS); + }, + configurable: true + }); // re-enable the schema version setter delete this._schemaVersionSet; @@ -1013,8 +920,7 @@ this.XPIDatabase = { * Return a list of all install locations known about by the database. This * is often a a subset of the total install locations when not all have * installed add-ons, occasionally a superset when an install location no - * longer exists. Only called from XPIProvider.processFileChanges, when - * the database should already be loaded. + * longer exists. * * @return a Set of names of install locations */ @@ -1030,62 +936,61 @@ this.XPIDatabase = { }, /** - * Asynchronously list all addons that match the filter function + * List all addons that match the filter function * @param aFilter * Function that takes an addon instance and returns * true if that addon should be included in the selected array - * @param aCallback - * Called back with an array of addons matching aFilter - * or an empty array if none match + * @return an array of DBAddonInternals */ - getAddonList: function(aFilter, aCallback) { - this.asyncLoadDB().then( - addonDB => { - let addonList = _filterDB(addonDB, aFilter); - asyncMap(addonList, getRepositoryAddon, safeCallback(aCallback)); - }) - .then(null, - error => { - ERROR("getAddonList failed", e); - safeCallback(aCallback)([]); - }); + _listAddons: function XPIDB_listAddons(aFilter) { + if (!this.addonDB) + return []; + + let addonList = []; + for (let [key, addon] of this.addonDB) { + if (aFilter(addon)) { + addonList.push(addon); + } + } + + return addonList; }, /** - * (Possibly asynchronously) get the first addon that matches the filter function + * Find the first addon that matches the filter function * @param aFilter * Function that takes an addon instance and returns * true if that addon should be selected - * @param aCallback - * Called back with the addon, or null if no matching addon is found + * @return The first DBAddonInternal for which the filter returns true */ - getAddon: function(aFilter, aCallback) { - return this.asyncLoadDB().then( - addonDB => { - getRepositoryAddon(_findAddon(addonDB, aFilter), safeCallback(aCallback)); - }) - .then(null, - error => { - ERROR("getAddon failed", e); - safeCallback(aCallback)(null); - }); + _findAddon: function XPIDB_findAddon(aFilter) { + if (!this.addonDB) + return null; + + for (let [key, addon] of this.addonDB) { + if (aFilter(addon)) { + return addon; + } + } + + return null; }, /** * Synchronously reads all the add-ons in a particular install location. - * Always called with the addon database already loaded. * * @param aLocation * The name of the install location * @return an array of DBAddonInternals */ getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) { - return _filterDB(this.addonDB, aAddon => (aAddon.location == aLocation)); + return this._listAddons(function inLocation(aAddon) {return (aAddon.location == aLocation);}); }, /** * Asynchronously gets an add-on with a particular ID in a particular * install location. + * XXX IRVING sync for now * * @param aId * The ID of the add-on to retrieve @@ -1095,13 +1000,12 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { - this.asyncLoadDB().then( - addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId), - safeCallback(aCallback))); + getRepositoryAddon(this.addonDB.get(aLocation + ":" + aId), aCallback); }, /** - * Asynchronously gets the add-on with the specified ID that is visible. + * Asynchronously gets the add-on with an ID that is visible. + * XXX IRVING sync * * @param aId * The ID of the add-on to retrieve @@ -1109,12 +1013,13 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) { - this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible), - aCallback); + let addon = this._findAddon(function visibleID(aAddon) {return ((aAddon.id == aId) && aAddon.visible)}); + getRepositoryAddon(addon, aCallback); }, /** * Asynchronously gets the visible add-ons, optionally restricting by type. + * XXX IRVING sync * * @param aTypes * An array of types to include or null to include all types @@ -1122,10 +1027,10 @@ this.XPIDatabase = { * A callback to pass the array of DBAddonInternals to */ getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) { - this.getAddonList(aAddon => (aAddon.visible && - (!aTypes || (aTypes.length == 0) || - (aTypes.indexOf(aAddon.type) > -1))), - aCallback); + let addons = this._listAddons(function visibleType(aAddon) { + return (aAddon.visible && (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) + }); + asyncMap(addons, getRepositoryAddon, aCallback); }, /** @@ -1136,14 +1041,7 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddonsByType: function XPIDB_getAddonsByType(aType) { - if (!this.addonDB) { - // jank-tastic! Must synchronously load DB if the theme switches from - // an XPI theme to a lightweight theme before the DB has loaded, - // because we're called from sync XPIProvider.addonChanged - WARN("Synchronous load of XPI database due to getAddonsByType(" + aType + ")"); - this.syncLoadDB(true); - } - return _filterDB(this.addonDB, aAddon => (aAddon.type == aType)); + return this._listAddons(function byType(aAddon) { return aAddon.type == aType; }); }, /** @@ -1154,20 +1052,14 @@ this.XPIDatabase = { * @return a DBAddonInternal */ getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) { - if (!this.addonDB) { - // This may be called when the DB hasn't otherwise been loaded - // XXX TELEMETRY - WARN("Synchronous load of XPI database due to getVisibleAddonForInternalName"); - this.syncLoadDB(true); - } - - return _findAddon(this.addonDB, - aAddon => aAddon.visible && - (aAddon.internalName == aInternalName)); + return this._findAddon(function visibleInternalName(aAddon) { + return (aAddon.visible && (aAddon.internalName == aInternalName)); + }); }, /** * Asynchronously gets all add-ons with pending operations. + * XXX IRVING sync * * @param aTypes * The types of add-ons to retrieve or null to get all types @@ -1177,19 +1069,21 @@ this.XPIDatabase = { getVisibleAddonsWithPendingOperations: function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) { - this.getAddonList( - aAddon => (aAddon.visible && - (aAddon.pendingUninstall || - // Logic here is tricky. If we're active but either - // disabled flag is set, we're pending disable; if we're not - // active and neither disabled flag is set, we're pending enable - (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && - (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))), - aCallback); + let addons = this._listAddons(function visibleType(aAddon) { + return (aAddon.visible && + (aAddon.pendingUninstall || + // Logic here is tricky. If we're active but either + // disabled flag is set, we're pending disable; if we're not + // active and neither disabled flag is set, we're pending enable + (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && + (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) + }); + asyncMap(addons, getRepositoryAddon, aCallback); }, /** * Asynchronously get an add-on by its Sync GUID. + * XXX IRVING sync * * @param aGUID * Sync GUID of add-on to fetch @@ -1199,23 +1093,17 @@ this.XPIDatabase = { * */ getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) { - this.getAddon(aAddon => aAddon.syncGUID == aGUID, - aCallback); + let addon = this._findAddon(function bySyncGUID(aAddon) { return aAddon.syncGUID == aGUID; }); + getRepositoryAddon(addon, aCallback); }, /** * Synchronously gets all add-ons in the database. - * This is only called from the preference observer for the default - * compatibility version preference, so we can return an empty list if - * we haven't loaded the database yet. * * @return an array of DBAddonInternals */ getAddons: function XPIDB_getAddons() { - if (!this.addonDB) { - return []; - } - return _filterDB(this.addonDB, aAddon => true); + return this._listAddons(function(aAddon) {return true;}); }, /** @@ -1228,10 +1116,12 @@ this.XPIDatabase = { * @return The DBAddonInternal that was added to the database */ addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) { - if (!this.addonDB) { - // XXX telemetry. Should never happen on platforms that have a default theme - this.syncLoadDB(false); - } + // If there is no DB yet then forcibly create one + // XXX IRVING I don't think this will work as expected because the addonDB + // getter will kick in. Might not matter because of the way the new DB + // creates itself. + if (!this.addonDB) + this.openConnection(false, true); let newAddon = new DBAddonInternal(aAddon); newAddon.descriptor = aDescriptor; @@ -1292,7 +1182,7 @@ this.XPIDatabase = { */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { LOG("Make addon " + aAddon._key + " visible"); - for (let [, otherAddon] of this.addonDB) { + for (let [key, otherAddon] of this.addonDB) { if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { LOG("Hide addon " + otherAddon._key); otherAddon.visible = false; @@ -1319,7 +1209,6 @@ this.XPIDatabase = { /** * Synchronously sets the Sync GUID for an add-on. - * Only called when the database is already loaded. * * @param aAddon * The DBAddonInternal being updated @@ -1332,7 +1221,7 @@ this.XPIDatabase = { function excludeSyncGUID(otherAddon) { return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID); } - let otherAddon = _findAddon(this.addonDB, excludeSyncGUID); + let otherAddon = this._findAddon(excludeSyncGUID); if (otherAddon) { throw new Error("Addon sync GUID conflict for addon " + aAddon._key + ": " + otherAddon._key + " already has GUID " + aGUID); @@ -1375,7 +1264,7 @@ this.XPIDatabase = { // XXX IRVING this may get called during XPI-utils shutdown // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); - for (let [, addon] of this.addonDB) { + for (let [key, addon] of this.addonDB) { let newActive = (addon.visible && !addon.userDisabled && !addon.softDisabled && !addon.appDisabled && !addon.pendingUninstall); @@ -1390,10 +1279,6 @@ this.XPIDatabase = { * Writes out the XPI add-ons list for the platform to read. */ writeAddonsList: function XPIDB_writeAddonsList() { - if (!this.addonDB) { - // Unusual condition, force the DB to load - this.syncLoadDB(true); - } Services.appinfo.invalidateCachesOnRestart(); let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], @@ -1403,9 +1288,9 @@ this.XPIDatabase = { let count = 0; let fullCount = 0; - let activeAddons = _filterDB( - this.addonDB, - aAddon => aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme")); + let activeAddons = this._listAddons(function active(aAddon) { + return aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme"); + }); for (let row of activeAddons) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; @@ -1425,13 +1310,12 @@ this.XPIDatabase = { let themes = []; if (dssEnabled) { - themes = _filterDB(this.addonDB, aAddon => aAddon.type == "theme"); + themes = this._listAddons(function isTheme(aAddon){ return aAddon.type == "theme"; }); } else { - let activeTheme = _findAddon( - this.addonDB, - aAddon => (aAddon.type == "theme") && - (aAddon.internalName == XPIProvider.selectedSkin)); + let activeTheme = this._findAddon(function isSelected(aAddon) { + return ((aAddon.type == "theme") && (aAddon.internalName == XPIProvider.selectedSkin)); + }); if (activeTheme) { themes.push(activeTheme); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js index 133c3a1990eb..4f52cf6ca6b5 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js @@ -45,26 +45,25 @@ function run_test() { let internal_ids = {}; - let a = ["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"]; - stmt.params.id = a[0]; - stmt.params.location = a[1]; - stmt.params.version = a[2]; - stmt.params.active = a[3]; - stmt.params.userDisabled = a[4]; - stmt.params.installDate = a[5]; - stmt.execute(); - internal_ids[a[0]] = db.lastInsertRowID; + [["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"] + ].forEach(function(a) { + stmt.params.id = a[0]; + stmt.params.location = a[1]; + stmt.params.version = a[2]; + stmt.params.active = a[3]; + stmt.params.userDisabled = a[4]; + stmt.params.installDate = a[5]; + stmt.execute(); + internal_ids[a[0]] = db.lastInsertRowID; + }); stmt.finalize(); - db.schemaVersion = 14; + db.schemaVersion = 15; Services.prefs.setIntPref("extensions.databaseSchema", 14); db.close(); startupManager(); - run_next_test(); -} -add_test(function before_rebuild() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function check_before_rebuild (a1) { // First check that it migrated OK once @@ -78,7 +77,7 @@ add_test(function before_rebuild() { run_next_test(); }); -}); +} // now shut down, remove the JSON database, // start up again, and make sure the data didn't migrate this time From 9ba406bab0d166b6f4f7bde9a7694aac90ac7436 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:20:05 +0200 Subject: [PATCH 27/40] Backed out changeset f3cf78d7e62d (bug 853388) --- toolkit/mozapps/extensions/Makefile.in | 2 +- .../mozapps/extensions/XPIProviderUtils.js | 35 +++--- .../extensions/test/xpcshell/test_migrate2.js | 4 +- .../test/xpcshell/test_migrate_max_version.js | 102 ------------------ .../extensions/test/xpcshell/xpcshell.ini | 1 - 5 files changed, 19 insertions(+), 125 deletions(-) delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js diff --git a/toolkit/mozapps/extensions/Makefile.in b/toolkit/mozapps/extensions/Makefile.in index 5eeb7947b6e1..9dbf13c811a2 100644 --- a/toolkit/mozapps/extensions/Makefile.in +++ b/toolkit/mozapps/extensions/Makefile.in @@ -15,7 +15,7 @@ endif # This is used in multiple places, so is defined here to avoid it getting # out of sync. -DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=15 +DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=14 # Additional debugging info is exposed in debug builds, or by setting the # MOZ_EM_DEBUG environment variable when building. diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 9147cb1b448e..5fe3142066f1 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -43,8 +43,6 @@ const FILE_XPI_ADDONS_LIST = "extensions.ini"; // The value for this is in Makefile.in #expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__; -// The last version of DB_SCHEMA implemented in SQLITE -const LAST_SQLITE_DB_SCHEMA = 14; const PREF_DB_SCHEMA = "extensions.databaseSchema"; const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons"; @@ -471,21 +469,26 @@ this.XPIDatabase = { * {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...} * if there is useful information */ - getMigrateDataFromSQLITE: function XPIDB_getMigrateDataFromSQLITE() { + loadSqliteData: function XPIDB_loadSqliteData() { let connection = null; let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); + if (!dbfile.exists()) { + return false; + } // Attempt to open the database try { connection = Services.storage.openUnsharedDatabase(dbfile); } catch (e) { + // exists but SQLITE can't open it WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e); - return null; + this.migrateData = null; + return true; } LOG("Migrating data from sqlite"); - let migrateData = this.getMigrateDataFromDatabase(connection); + this.migrateData = this.getMigrateDataFromDatabase(connection); connection.close(); - return migrateData; + return true; }, /** @@ -575,18 +578,13 @@ this.XPIDatabase = { } catch (e) { if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { - try { - let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); - if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { - // we should have an older SQLITE database - this.migrateData = this.getMigrateDataFromSQLITE(); - } - // else we've upgraded before but the JSON file is gone, fall through - // and rebuild from scratch - } - catch(e) { - // No schema version pref means either a really old upgrade (RDF) or - // a new profile + // XXX re-implement logic to decide whether to upgrade database + // by checking the DB_SCHEMA_VERSION preference. + // Fall back to attempting database upgrades + WARN("Extensions database not found; attempting to upgrade"); + // See if there is SQLITE to migrate from + if (!this.loadSqliteData()) { + // Nope, try RDF this.migrateData = this.getMigrateDataFromRDF(); } @@ -597,7 +595,6 @@ this.XPIDatabase = { " exists but is not readable; rebuilding in memory", e); // XXX open question - if we can overwrite at save time, should we, or should we // leave the locked database in case we can recover from it next time we start up? - // The old code made one attempt to remove the locked file before it rebuilt in memory this.lockedDatabase = true; // XXX TELEMETRY report when this happens? this.rebuildDatabase(aRebuildOnError); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js index c703b25c19a9..20431a9a7d00 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we migrate data from SQLITE databases +// Checks that we migrate data from future versions of the database // Note that since the database doesn't contain the foreignInstall field we // should just assume that no add-ons in the user profile were foreignInstalls @@ -177,7 +177,7 @@ function run_test() { stmt.finalize(); db.schemaVersion = 10000; - Services.prefs.setIntPref("extensions.databaseSchema", 14); + Services.prefs.setIntPref("extensions.databaseSchema", 100); db.close(); startupManager(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js deleted file mode 100644 index 4f52cf6ca6b5..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js +++ /dev/null @@ -1,102 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// Checks that we don't migrate data from SQLITE if -// the "extensions.databaseSchema" preference shows we've -// already upgraded to JSON - -// Enable loading extensions from the user and system scopes -Services.prefs.setIntPref("extensions.enabledScopes", - AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER + - AddonManager.SCOPE_SYSTEM); - -var addon1 = { - id: "addon1@tests.mozilla.org", - version: "1.0", - name: "Test 1", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }] -}; - -createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); -const profileDir = gProfD.clone(); -profileDir.append("extensions"); - -function run_test() { - writeInstallRDFForExtension(addon1, profileDir); - - // Write out a minimal database - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.createTable("addon", "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "id TEXT, location TEXT, version TEXT, active INTEGER, " + - "userDisabled INTEGER, installDate INTEGER"); - db.createTable("targetApplication", "addon_internal_id INTEGER, " + - "id TEXT, minVersion TEXT, maxVersion TEXT"); - let stmt = db.createStatement("INSERT INTO addon VALUES (NULL, :id, :location, " + - ":version, :active, :userDisabled, :installDate)"); - - let internal_ids = {}; - - [["addon1@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"] - ].forEach(function(a) { - stmt.params.id = a[0]; - stmt.params.location = a[1]; - stmt.params.version = a[2]; - stmt.params.active = a[3]; - stmt.params.userDisabled = a[4]; - stmt.params.installDate = a[5]; - stmt.execute(); - internal_ids[a[0]] = db.lastInsertRowID; - }); - stmt.finalize(); - - db.schemaVersion = 15; - Services.prefs.setIntPref("extensions.databaseSchema", 14); - db.close(); - - startupManager(); - - AddonManager.getAddonByID("addon1@tests.mozilla.org", - function check_before_rebuild (a1) { - // First check that it migrated OK once - // addon1 was disabled in the database - do_check_neq(a1, null); - do_check_true(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_false(a1.isActive); - do_check_false(a1.strictCompatibility); - do_check_false(a1.foreignInstall); - - run_next_test(); - }); -} - -// now shut down, remove the JSON database, -// start up again, and make sure the data didn't migrate this time -add_test(function rebuild_again() { - shutdownManager(); - gExtensionsJSON.remove(true); - startupManager(); - - AddonManager.getAddonByID("addon1@tests.mozilla.org", - function check_after_rebuild(a1) { - // addon1 was rebuilt from extensions directory, - // so it appears enabled as a foreign install - do_check_neq(a1, null); - do_check_false(a1.userDisabled); - do_check_false(a1.appDisabled); - do_check_true(a1.isActive); - do_check_false(a1.strictCompatibility); - do_check_true(a1.foreignInstall); - - run_next_test(); - }); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 982aa9705982..6113454b47a2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -205,7 +205,6 @@ skip-if = os == "android" [test_migrate4.js] [test_migrate5.js] [test_migrateAddonRepository.js] -[test_migrate_max_version.js] [test_onPropertyChanged_appDisabled.js] [test_permissions.js] [test_plugins.js] From 372ff7f1c155334cfe7454bd60761055943e4b4f Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:20:06 +0200 Subject: [PATCH 28/40] Backed out changeset ee904d911f2d (bug 853388) --- toolkit/mozapps/extensions/XPIProvider.jsm | 151 +++++---- .../mozapps/extensions/XPIProviderUtils.js | 290 ++++++++++-------- .../xpcshell/test_AddonRepository_cache.js | 12 +- .../test/xpcshell/test_bootstrap.js | 5 +- .../test/xpcshell/test_bug559800.js | 3 - .../test/xpcshell/test_bug659772.js | 12 +- .../extensions/test/xpcshell/test_startup.js | 6 +- 7 files changed, 272 insertions(+), 207 deletions(-) diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 83adbdc628b6..2794fd309749 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -509,9 +509,6 @@ function findClosestLocale(aLocales) { * previous instance may be a previous install or in the case of an application * version change the same add-on. * - * NOTE: this may modify aNewAddon in place; callers should save the database if - * necessary - * * @param aOldAddon * The previous instance of the add-on * @param aNewAddon @@ -1335,24 +1332,19 @@ function recursiveRemove(aFile) { * @return Epoch time, as described above. 0 for an empty directory. */ function recursiveLastModifiedTime(aFile) { - try { - if (aFile.isFile()) - return aFile.lastModifiedTime; + if (aFile.isFile()) + return aFile.lastModifiedTime; - if (aFile.isDirectory()) { - let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - let entry, time; - let maxTime = aFile.lastModifiedTime; - while ((entry = entries.nextFile)) { - time = recursiveLastModifiedTime(entry); - maxTime = Math.max(time, maxTime); - } - entries.close(); - return maxTime; + if (aFile.isDirectory()) { + let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); + let entry, time; + let maxTime = aFile.lastModifiedTime; + while ((entry = entries.nextFile)) { + time = recursiveLastModifiedTime(entry); + maxTime = Math.max(time, maxTime); } - } - catch (e) { - WARN("Problem getting last modified time for " + aFile.path, e); + entries.close(); + return maxTime; } // If the file is something else, just ignore it. @@ -1885,12 +1877,10 @@ var XPIProvider = { if (gLazyObjectsLoaded) { XPIDatabase.shutdown(function shutdownCallback() { - LOG("Notifying XPI shutdown observers"); Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); }); } else { - LOG("Notifying XPI shutdown observers"); Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); } }, @@ -1948,7 +1938,7 @@ var XPIProvider = { }, /** - * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref). + * Persists changes to XPIProvider.bootstrappedAddons to it's store (a pref). */ persistBootstrappedAddons: function XPI_persistBootstrappedAddons() { Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, @@ -2508,7 +2498,7 @@ var XPIProvider = { applyBlocklistChanges(aOldAddon, newAddon); // Carry over any pendingUninstall state to add-ons modified directly - // in the profile. This is important when the attempt to remove the + // in the profile. This is impoprtant when the attempt to remove the // add-on in processPendingFileChanges failed and caused an mtime // change to the add-ons files. newAddon.pendingUninstall = aOldAddon.pendingUninstall; @@ -2668,27 +2658,38 @@ var XPIProvider = { // App version changed, we may need to update the appDisabled property. if (aUpdateCompatibility) { - let wasDisabled = isAddonDisabled(aOldAddon); - let wasAppDisabled = aOldAddon.appDisabled; - let wasUserDisabled = aOldAddon.userDisabled; - let wasSoftDisabled = aOldAddon.softDisabled; + // Create a basic add-on object for the new state to save reproducing + // the applyBlocklistChanges code + let newAddon = new AddonInternal(); + newAddon.id = aOldAddon.id; + newAddon.syncGUID = aOldAddon.syncGUID; + newAddon.version = aOldAddon.version; + newAddon.type = aOldAddon.type; + newAddon.appDisabled = !isUsableAddon(aOldAddon); - // This updates the addon's JSON cached data in place - applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion, + // Sync the userDisabled flag to the selectedSkin + if (aOldAddon.type == "theme") + newAddon.userDisabled = aOldAddon.internalName != XPIProvider.selectedSkin; + + applyBlocklistChanges(aOldAddon, newAddon, aOldAppVersion, aOldPlatformVersion); - aOldAddon.appDisabled = !isUsableAddon(aOldAddon); - let isDisabled = isAddonDisabled(aOldAddon); + let wasDisabled = isAddonDisabled(aOldAddon); + let isDisabled = isAddonDisabled(newAddon); // If either property has changed update the database. - if (wasAppDisabled != aOldAddon.appDisabled || - wasUserDisabled != aOldAddon.userDisabled || - wasSoftDisabled != aOldAddon.softDisabled) { + if (newAddon.appDisabled != aOldAddon.appDisabled || + newAddon.userDisabled != aOldAddon.userDisabled || + newAddon.softDisabled != aOldAddon.softDisabled) { LOG("Add-on " + aOldAddon.id + " changed appDisabled state to " + - aOldAddon.appDisabled + ", userDisabled state to " + - aOldAddon.userDisabled + " and softDisabled state to " + - aOldAddon.softDisabled); - XPIDatabase.saveChanges(); + newAddon.appDisabled + ", userDisabled state to " + + newAddon.userDisabled + " and softDisabled state to " + + newAddon.softDisabled); + XPIDatabase.setAddonProperties(aOldAddon, { + appDisabled: newAddon.appDisabled, + userDisabled: newAddon.userDisabled, + softDisabled: newAddon.softDisabled + }); } // If this is a visible add-on and it has changed disabled state then we @@ -2894,7 +2895,20 @@ var XPIProvider = { newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon)) } - let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); + let newDBAddon = null; + try { + // Update the database. + // XXX I don't think this can throw any more + newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); + } + catch (e) { + // Failing to write the add-on into the database is non-fatal, the + // add-on will just be unavailable until we try again in a subsequent + // startup + ERROR("Failed to add add-on " + aId + " in " + aInstallLocation.name + + " to database", e); + return false; + } if (newDBAddon.visible) { // Remember add-ons that were first detected during startup. @@ -3212,22 +3226,25 @@ var XPIProvider = { } } - // Catch and log any errors during the main startup + // Catch any errors during the main startup and rollback the database changes + let transationBegun = false; try { let extensionListChanged = false; // If the database needs to be updated then open it and then update it // from the filesystem if (updateDatabase || hasPendingChanges) { - try { - XPIDatabase.openConnection(false, true); + XPIDatabase.beginTransaction(); + transationBegun = true; + XPIDatabase.openConnection(false, true); + try { extensionListChanged = this.processFileChanges(state, manifests, aAppChanged, aOldAppVersion, aOldPlatformVersion); } catch (e) { - ERROR("Failed to process extension changes at startup", e); + ERROR("Error processing file changes", e); } } AddonManagerPrivate.recordSimpleMeasure("installedUnpacked", this.unpackedAddons); @@ -3236,6 +3253,10 @@ var XPIProvider = { // When upgrading the app and using a custom skin make sure it is still // compatible otherwise switch back the default if (this.currentSkin != this.defaultSkin) { + if (!transationBegun) { + XPIDatabase.beginTransaction(); + transationBegun = true; + } let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin); if (!oldSkin || isAddonDisabled(oldSkin)) this.enableDefaultTheme(); @@ -3243,21 +3264,21 @@ var XPIProvider = { // When upgrading remove the old extensions cache to force older // versions to rescan the entire list of extensions - try { - let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true); - if (oldCache.exists()) - oldCache.remove(true); - } - catch (e) { - WARN("Unable to remove old extension cache " + oldCache.path, e); - } + let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true); + if (oldCache.exists()) + oldCache.remove(true); } // If the application crashed before completing any pending operations then // we should perform them now. if (extensionListChanged || hasPendingChanges) { LOG("Updating database with changes to installed add-ons"); + if (!transationBegun) { + XPIDatabase.beginTransaction(); + transationBegun = true; + } XPIDatabase.updateActiveAddons(); + XPIDatabase.commitTransaction(); XPIDatabase.writeAddonsList(); Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false); Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, @@ -3266,9 +3287,14 @@ var XPIProvider = { } LOG("No changes found"); + if (transationBegun) + XPIDatabase.commitTransaction(); } catch (e) { - ERROR("Error during startup file checks", e); + ERROR("Error during startup file checks, rolling back any database " + + "changes", e); + if (transationBegun) + XPIDatabase.rollbackTransaction(); } // Check that the add-ons list still exists @@ -3657,7 +3683,7 @@ var XPIProvider = { null); this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); - this.updateAddonAppDisabledStates(); + this.updateAllAddonDisabledStates(); break; } }, @@ -3976,7 +4002,8 @@ var XPIProvider = { this.bootstrapScopes[aId][aMethod](params, aReason); } catch (e) { - WARN("Exception running bootstrap method " + aMethod + " on " + aId, e); + WARN("Exception running bootstrap method " + aMethod + " on " + + aId, e); } } finally { @@ -3987,10 +4014,20 @@ var XPIProvider = { } }, + /** + * Updates the appDisabled property for all add-ons. + */ + updateAllAddonDisabledStates: function XPI_updateAllAddonDisabledStates() { + let addons = XPIDatabase.getAddons(); + addons.forEach(function(aAddon) { + this.updateAddonDisabledState(aAddon); + }, this); + }, + /** * Updates the disabled state for an add-on. Its appDisabled property will be - * calculated and if the add-on is changed the database will be saved and - * appropriate notifications will be sent out to the registered AddonListeners. + * calculated and if the add-on is changed appropriate notifications will be + * sent out to the registered AddonListeners. * * @param aAddon * The DBAddonInternal to update @@ -5297,7 +5334,7 @@ AddonInstall.prototype = { // Update the metadata in the database this.addon._sourceBundle = file; this.addon._installLocation = this.installLocation; - this.addon.updateDate = recursiveLastModifiedTime(file); // XXX sync recursive scan + this.addon.updateDate = recursiveLastModifiedTime(file); this.addon.visible = true; if (isUpgrade) { this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 5fe3142066f1..87c0d910f014 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -7,24 +7,19 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; -const Cu = Components.utils; -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/AddonRepository.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave", - "resource://gre/modules/DeferredSave.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/Promise.jsm"); ["LOG", "WARN", "ERROR"].forEach(function(aName) { Object.defineProperty(this, aName, { get: function logFuncGetter () { - Cu.import("resource://gre/modules/AddonLogging.jsm"); + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); LogManager.getLogger("addons.xpi-utils", this); return this[aName]; @@ -92,8 +87,6 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "strictCompatibility", "locales", "targetApplications", "targetPlatforms"]; -// Time to wait before async save of XPI JSON database, in milliseconds -const ASYNC_SAVE_DELAY_MS = 20; const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" @@ -349,17 +342,18 @@ function DBAddonInternal(aLoaded) { DBAddonInternal.prototype = { applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { + XPIDatabase.beginTransaction(); this.targetApplications.forEach(function(aTargetApp) { aUpdate.targetApplications.forEach(function(aUpdateTarget) { if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { aTargetApp.minVersion = aUpdateTarget.minVersion; aTargetApp.maxVersion = aUpdateTarget.maxVersion; - XPIDatabase.saveChanges(); } }); }); XPIProvider.updateAddonDisabledState(this); + XPIDatabase.commitTransaction(); }, get inDatabase() { @@ -376,6 +370,8 @@ DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; this.XPIDatabase = { // true if the database connection has been opened initialized: false, + // The nested transaction count + transactionCount: 0, // The database file jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), // Migration data loaded from an old version of the database. @@ -395,59 +391,20 @@ this.XPIDatabase = { return this.dbfileExists = aValue; }, - /** - * Mark the current stored data dirty, and schedule a flush to disk - */ - saveChanges: function() { - if (!this.initialized) { - throw new Error("Attempt to use XPI database when it is not initialized"); - } - - // handle the "in memory only" case - if (this.lockedDatabase) { - return; - } - - let promise = this._deferredSave.saveChanges(); - if (!this._schemaVersionSet) { - this._schemaVersionSet = true; - promise.then( - count => { - // Update the XPIDB schema version preference the first time we successfully - // save the database. - LOG("XPI Database saved, setting schema version preference to " + DB_SCHEMA); - Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); - }, - error => { - // Need to try setting the schema version again later - this._schemaVersionSet = false; - WARN("Failed to save XPI database", error); - }); - } - }, - - flush: function() { - // handle the "in memory only" case - if (this.lockedDatabase) { - let done = Promise.defer(); - done.resolve(0); - return done.promise; - } - - return this._deferredSave.flush(); - }, - - get _deferredSave() { - delete this._deferredSave; - return this._deferredSave = - new DeferredSave(this.jsonFile.path, () => JSON.stringify(this), - ASYNC_SAVE_DELAY_MS); - }, - /** * Converts the current internal state of the XPI addon database to JSON + * and writes it to the user's profile. Synchronous for now, eventually must + * be async, reliable, etc. + * XXX should we remove the JSON file if it would be empty? Not sure if that + * would ever happen, given the default theme */ - toJSON: function() { + writeJSON: function XPIDB_writeJSON() { + // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet + + // Don't mess with an existing database on disk, if it was locked at start up + if (this.lockedDatabase) + return; + let addons = []; for (let [key, addon] of this.addonDB) { addons.push(addon); @@ -456,7 +413,74 @@ this.XPIDatabase = { schemaVersion: DB_SCHEMA, addons: addons }; - return toSave; + + let stream = FileUtils.openSafeFileOutputStream(this.jsonFile); + let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. + createInstance(Ci.nsIConverterOutputStream); + try { + converter.init(stream, "UTF-8", 0, 0x0000); + // XXX pretty print the JSON while debugging + let out = JSON.stringify(toSave, null, 2); + // dump("Writing JSON:\n" + out + "\n"); + converter.writeString(out); + converter.flush(); + // nsConverterOutputStream doesn't finish() safe output streams on close() + FileUtils.closeSafeFileOutputStream(stream); + converter.close(); + this.dbfileExists = true; + // XXX probably only want to do this if the version is different + Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); + Services.prefs.savePrefFile(null); // XXX is this bad sync I/O? + } + catch(e) { + ERROR("Failed to save database to JSON", e); + stream.close(); + } + }, + + /** + * Begins a new transaction in the database. Transactions may be nested. Data + * written by an inner transaction may be rolled back on its own. Rolling back + * an outer transaction will rollback all the changes made by inner + * transactions even if they were committed. No data is written to the disk + * until the outermost transaction is committed. Transactions can be started + * even when the database is not yet open in which case they will be started + * when the database is first opened. + */ + beginTransaction: function XPIDB_beginTransaction() { + this.transactionCount++; + }, + + /** + * Commits the most recent transaction. The data may still be rolled back if + * an outer transaction is rolled back. + */ + commitTransaction: function XPIDB_commitTransaction() { + if (this.transactionCount == 0) { + ERROR("Attempt to commit one transaction too many."); + return; + } + + this.transactionCount--; + + if (this.transactionCount == 0) { + // All our nested transactions are done, write the JSON file + this.writeJSON(); + } + }, + + /** + * Rolls back the most recent transaction. The database will return to its + * state when the transaction was started. + */ + rollbackTransaction: function XPIDB_rollbackTransaction() { + if (this.transactionCount == 0) { + ERROR("Attempt to rollback one transaction too many."); + return; + } + + this.transactionCount--; + // XXX IRVING we don't handle rollback in the JSON store }, /** @@ -542,10 +566,9 @@ this.XPIDatabase = { this.rebuildDatabase(aRebuildOnError); } if (inputAddons.schemaVersion != DB_SCHEMA) { - // Handle mismatched JSON schema version. For now, we assume - // compatibility for JSON data, though we throw away any fields we - // don't know about - // XXX preserve unknown fields during save/restore + // Handle mismatched JSON schema version. For now, we assume backward/forward + // compatibility as long as we preserve unknown fields during save & restore + // XXX preserve schema version and unknown fields during save/restore LOG("JSON schema mismatch: expected " + DB_SCHEMA + ", actual " + inputAddons.schemaVersion); } @@ -558,7 +581,6 @@ this.XPIDatabase = { }); this.addonDB = addonDB; LOG("Successfully read XPI database"); - this.initialized = true; } catch(e) { // If we catch and log a SyntaxError from the JSON @@ -605,6 +627,7 @@ this.XPIDatabase = { fstream.close(); } + this.initialized = true; return; // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"? @@ -624,25 +647,26 @@ this.XPIDatabase = { * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) { - this.addonDB = new Map(); - this.initialized = true; - // If there is no migration data then load the list of add-on directories // that were active during the last run + this.addonDB = new Map(); if (!this.migrateData) this.activeBundles = this.getActiveBundles(); if (aRebuildOnError) { WARN("Rebuilding add-ons database from installed extensions."); + this.beginTransaction(); try { let state = XPIProvider.getInstallLocationStates(); XPIProvider.processFileChanges(state, {}, false); + // Make sure to update the active add-ons and add-ons list on shutdown + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + this.commitTransaction(); } catch (e) { - ERROR("Failed to rebuild XPI database from installed extensions", e); + ERROR("Error processing file changes", e); + this.rollbackTransaction(); } - // Make to update the active add-ons and add-ons list on shutdown - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); } }, @@ -861,55 +885,38 @@ this.XPIDatabase = { shutdown: function XPIDB_shutdown(aCallback) { LOG("shutdown"); if (this.initialized) { + if (this.transactionCount > 0) { + ERROR(this.transactionCount + " outstanding transactions, rolling back."); + while (this.transactionCount > 0) + this.rollbackTransaction(); + } + // If we are running with an in-memory database then force a new // extensions.ini to be written to disk on the next startup if (this.lockedDatabase) Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); this.initialized = false; - let result = null; - // Make sure any pending writes of the DB are complete, and we - // finish cleaning up, and then call back - this.flush() - .then(null, error => { - ERROR("Flush of XPI database failed", error); - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - result = error; - return 0; - }) - .then(count => { - // Clear out the cached addons data loaded from JSON and recreate - // the getter to allow database re-loads during testing. - delete this.addonDB; - Object.defineProperty(this, "addonDB", { - get: function addonsGetter() { - this.openConnection(true); - return this.addonDB; - }, - configurable: true - }); - // same for the deferred save - delete this._deferredSave; - Object.defineProperty(this, "_deferredSave", { - set: function deferredSaveGetter() { - delete this._deferredSave; - return this._deferredSave = - new DeferredSave(this.jsonFile.path, this.formJSON.bind(this), - ASYNC_SAVE_DELAY_MS); - }, - configurable: true - }); - // re-enable the schema version setter - delete this._schemaVersionSet; - - if (aCallback) - aCallback(result); - }); + // Clear out the cached addons data loaded from JSON and recreate + // the getter to allow database re-loads during testing. + delete this.addonDB; + Object.defineProperty(this, "addonDB", { + get: function addonsGetter() { + this.openConnection(true); + return this.addonDB; + }, + configurable: true + }); + // XXX IRVING removed an async callback when the database was closed + // XXX do we want to keep the ability to async flush extensions.json + // XXX and then call back? + if (aCallback) + aCallback(); } else { if (aCallback) - aCallback(null); + aCallback(); } }, @@ -1120,6 +1127,8 @@ this.XPIDatabase = { if (!this.addonDB) this.openConnection(false, true); + this.beginTransaction(); + let newAddon = new DBAddonInternal(aAddon); newAddon.descriptor = aDescriptor; this.addonDB.set(newAddon._key, newAddon); @@ -1127,12 +1136,12 @@ this.XPIDatabase = { this.makeAddonVisible(newAddon); } - this.saveChanges(); + this.commitTransaction(); return newAddon; }, /** - * Synchronously updates an add-on's metadata in the database. Currently just + * Synchronously updates an add-ons metadata in the database. Currently just * removes and recreates. * * @param aOldAddon @@ -1145,16 +1154,26 @@ this.XPIDatabase = { */ updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon, aDescriptor) { - this.removeAddonMetadata(aOldAddon); - aNewAddon.syncGUID = aOldAddon.syncGUID; - aNewAddon.installDate = aOldAddon.installDate; - aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates; - aNewAddon.foreignInstall = aOldAddon.foreignInstall; - aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && - !aNewAddon.appDisabled && !aNewAddon.pendingUninstall); + this.beginTransaction(); - // addAddonMetadata does a saveChanges() - return this.addAddonMetadata(aNewAddon, aDescriptor); + // Any errors in here should rollback the transaction + try { + this.removeAddonMetadata(aOldAddon); + aNewAddon.syncGUID = aOldAddon.syncGUID; + aNewAddon.installDate = aOldAddon.installDate; + aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates; + aNewAddon.foreignInstall = aOldAddon.foreignInstall; + aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && + !aNewAddon.appDisabled && !aNewAddon.pendingUninstall) + + let newDBAddon = this.addAddonMetadata(aNewAddon, aDescriptor); + this.commitTransaction(); + return newDBAddon; + } + catch (e) { + this.rollbackTransaction(); + throw e; + } }, /** @@ -1164,8 +1183,9 @@ this.XPIDatabase = { * The DBAddonInternal being removed */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { + this.beginTransaction(); this.addonDB.delete(aAddon._key); - this.saveChanges(); + this.commitTransaction(); }, /** @@ -1178,6 +1198,7 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { + this.beginTransaction(); LOG("Make addon " + aAddon._key + " visible"); for (let [key, otherAddon] of this.addonDB) { if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { @@ -1186,7 +1207,7 @@ this.XPIDatabase = { } } aAddon.visible = true; - this.saveChanges(); + this.commitTransaction(); }, /** @@ -1198,10 +1219,11 @@ this.XPIDatabase = { * A dictionary of properties to set */ setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) { + this.beginTransaction(); for (let key in aProperties) { aAddon[key] = aProperties[key]; } - this.saveChanges(); + this.commitTransaction(); }, /** @@ -1223,8 +1245,9 @@ this.XPIDatabase = { throw new Error("Addon sync GUID conflict for addon " + aAddon._key + ": " + otherAddon._key + " already has GUID " + aGUID); } + this.beginTransaction(); aAddon.syncGUID = aGUID; - this.saveChanges(); + this.commitTransaction(); }, /** @@ -1237,8 +1260,9 @@ this.XPIDatabase = { * File path of the installed addon */ setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { + this.beginTransaction(); aAddon.descriptor = aDescriptor; - this.saveChanges(); + this.commitTransaction(); }, /** @@ -1250,8 +1274,9 @@ this.XPIDatabase = { updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) { LOG("Updating active state for add-on " + aAddon.id + " to " + aActive); + this.beginTransaction(); aAddon.active = aActive; - this.saveChanges(); + this.commitTransaction(); }, /** @@ -1261,15 +1286,20 @@ this.XPIDatabase = { // XXX IRVING this may get called during XPI-utils shutdown // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); + let changed = false; for (let [key, addon] of this.addonDB) { let newActive = (addon.visible && !addon.userDisabled && !addon.softDisabled && !addon.appDisabled && !addon.pendingUninstall); if (newActive != addon.active) { addon.active = newActive; - this.saveChanges(); + changed = true; } } + if (changed) { + this.beginTransaction(); + this.commitTransaction(); + } }, /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index 600f3d81b3f0..14d302a4b1e5 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -512,7 +512,7 @@ function check_cache(aExpectedToFind, aExpectedImmediately, aCallback) { * A callback to call once the checks are complete */ function check_initialized_cache(aExpectedToFind, aCallback) { - check_cache(aExpectedToFind, true, function restart_initialized_cache() { + check_cache(aExpectedToFind, true, function() { restartManager(); // If cache is disabled, then expect results immediately @@ -534,13 +534,13 @@ function waitForFlushedData(aCallback) { function run_test() { // Setup for test - do_test_pending("test_AddonRepository_cache"); + do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); startupManager(); // Install XPI add-ons - installAllFiles(ADDON_FILES, function first_installs() { + installAllFiles(ADDON_FILES, function() { restartManager(); gServer = new HttpServer(); @@ -552,7 +552,7 @@ function run_test() { } function end_test() { - gServer.stop(function() {do_test_finished("test_AddonRepository_cache");}); + gServer.stop(do_test_finished); } // Tests AddonRepository.cacheEnabled @@ -578,7 +578,7 @@ function run_test_3() { Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED); - AddonRepository.repopulateCache(ADDON_IDS, function test_3_repopulated() { + AddonRepository.repopulateCache(ADDON_IDS, function() { check_initialized_cache([false, false, false], run_test_4); }); } @@ -695,7 +695,7 @@ function run_test_12() { Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); - AddonManager.getAddonsByIDs(ADDON_IDS, function test_12_check(aAddons) { + AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { check_results(aAddons, WITHOUT_CACHE); do_execute_soon(run_test_13); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index 912c676f8e14..9a72b50c6733 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -204,6 +204,8 @@ function run_test_1() { } function check_test_1(installSyncGUID) { + do_check_true(gExtensionsJSON.exists()); + let file = gProfD.clone(); file.leafName = "extensions.ini"; do_check_false(file.exists()); @@ -350,9 +352,6 @@ function run_test_4() { // Tests that a restart shuts down and restarts the add-on function run_test_5() { shutdownManager(); - // By the time we've shut down, the database must have been written - do_check_true(gExtensionsJSON.exists()); - do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 0); do_check_eq(getShutdownReason(), APP_SHUTDOWN); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index 741debb76b4c..af1c845f19ff 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -60,9 +60,6 @@ function check_test_1() { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); - // due to delayed write, the file may not exist until - // after shutdown - shutdownManager(); do_check_true(gExtensionsJSON.exists()); do_check_true(gExtensionsJSON.fileSize > 0); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index 46b3e0393d2c..92ae8b21dc14 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -167,10 +167,10 @@ function run_test_1() { // the previous version of the DB do_check_neq(a3, null); do_check_eq(a3.version, "2.0"); - todo_check_false(a3.appDisabled); // XXX unresolved issue + do_check_false(a3.appDisabled); do_check_false(a3.userDisabled); - todo_check_true(a3.isActive); // XXX same - todo_check_true(isExtensionInAddonsList(profileDir, addon3.id)); // XXX same + do_check_true(a3.isActive); + do_check_true(isExtensionInAddonsList(profileDir, addon3.id)); do_check_neq(a4, null); do_check_eq(a4.version, "2.0"); @@ -309,10 +309,10 @@ function run_test_2() { // the previous version of the DB do_check_neq(a3, null); do_check_eq(a3.version, "2.0"); - todo_check_true(a3.appDisabled); + do_check_true(a3.appDisabled); do_check_false(a3.userDisabled); - todo_check_false(a3.isActive); - todo_check_false(isExtensionInAddonsList(profileDir, addon3.id)); + do_check_false(a3.isActive); + do_check_false(isExtensionInAddonsList(profileDir, addon3.id)); do_check_neq(a4, null); do_check_eq(a4.version, "2.0"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index a14f07e43471..841a50224169 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -191,8 +191,10 @@ function run_test_1() { do_check_true(gCachePurged); let file = gProfD.clone(); - file.append = "extensions.ini"; - do_print("Checking for " + file.path); + file.append("extensions.json"); + do_check_true(file.exists()); + + file.leafName = "extensions.ini"; do_check_true(file.exists()); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", From cd4d374148e7161e525e5be27ad87b74aba760eb Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:20:07 +0200 Subject: [PATCH 29/40] Backed out changeset 5861a7f63f25 (bug 853388) --- toolkit/mozapps/extensions/XPIProvider.jsm | 30 +- .../mozapps/extensions/XPIProviderUtils.js | 609 +++++++++++------- .../extensions/test/xpcshell/head_addons.js | 10 +- .../extensions/test/xpcshell/test_bad_json.js | 54 -- .../test/xpcshell/test_bootstrap.js | 10 +- .../test/xpcshell/test_bug559800.js | 10 +- .../extensions/test/xpcshell/test_corrupt.js | 6 +- .../xpcshell/test_corrupt_strictcompat.js | 6 +- .../extensions/test/xpcshell/test_locked.js | 13 +- .../extensions/test/xpcshell/test_locked2.js | 12 +- .../test/xpcshell/test_locked_strictcompat.js | 12 +- .../extensions/test/xpcshell/xpcshell.ini | 27 +- 12 files changed, 479 insertions(+), 320 deletions(-) delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 2794fd309749..ca763ec78741 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -595,8 +595,14 @@ function isAddonDisabled(aAddon) { return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled; } -XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1", - Ci.nsIRDFService); +Object.defineProperty(this, "gRDF", { + get: function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); + }, + configurable: true +}); function EM_R(aProperty) { return gRDF.GetResource(PREFIX_NS_EM + aProperty); @@ -1759,7 +1765,7 @@ var XPIProvider = { null); this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); - this.enabledAddons = ""; + this.enabledAddons = []; Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false); Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false); @@ -2898,7 +2904,6 @@ var XPIProvider = { let newDBAddon = null; try { // Update the database. - // XXX I don't think this can throw any more newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); } catch (e) { @@ -3000,8 +3005,9 @@ var XPIProvider = { let addonStates = aSt.addons; // Check if the database knows about any add-ons in this install location. - if (knownLocations.has(installLocation.name)) { - knownLocations.delete(installLocation.name); + let pos = knownLocations.indexOf(installLocation.name); + if (pos >= 0) { + knownLocations.splice(pos, 1); let addons = XPIDatabase.getAddonsInLocation(installLocation.name); // Iterate through the add-ons installed the last time the application // ran @@ -3078,12 +3084,12 @@ var XPIProvider = { // have any add-ons installed in them, or the locations no longer exist. // The metadata for the add-ons that were in them must be removed from the // database. - for (let location of knownLocations) { - let addons = XPIDatabase.getAddonsInLocation(location); + knownLocations.forEach(function(aLocation) { + let addons = XPIDatabase.getAddonsInLocation(aLocation); addons.forEach(function(aOldAddon) { changed = removeMetadata(aOldAddon) || changed; }, this); - } + }, this); // Tell Telemetry what we found AddonManagerPrivate.recordSimpleMeasure("modifiedUnpacked", modifiedUnpacked); @@ -5373,8 +5379,6 @@ AddonInstall.prototype = { reason, extraParams); } else { - // XXX this makes it dangerous to do many things in onInstallEnded - // listeners because important cleanup hasn't been done yet XPIProvider.unloadBootstrapScope(this.addon.id); } } @@ -5752,8 +5756,8 @@ UpdateChecker.prototype = { /** * The AddonInternal is an internal only representation of add-ons. It may - * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm) - * or an install manifest. + * have come from the database (see DBAddonInternal below) or an install + * manifest. */ function AddonInternal() { } diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 87c0d910f014..465b663058c5 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -16,6 +16,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm"); + ["LOG", "WARN", "ERROR"].forEach(function(aName) { Object.defineProperty(this, aName, { get: function logFuncGetter () { @@ -92,8 +93,14 @@ const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; -XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1", - Ci.nsIRDFService); +Object.defineProperty(this, "gRDF", { + get: function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); + }, + configurable: true +}); function EM_R(aProperty) { return gRDF.GetResource(PREFIX_NS_EM + aProperty); @@ -131,6 +138,60 @@ function getRDFProperty(aDs, aResource, aProperty) { return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true)); } + +/** + * A mozIStorageStatementCallback that will asynchronously build DBAddonInternal + * instances from the results it receives. Once the statement has completed + * executing and all of the metadata for all of the add-ons has been retrieved + * they will be passed as an array to aCallback. + * + * @param aCallback + * A callback function to pass the array of DBAddonInternals to + */ +function AsyncAddonListCallback(aCallback) { + this.callback = aCallback; + this.addons = []; +} + +AsyncAddonListCallback.prototype = { + callback: null, + complete: false, + count: 0, + addons: null, + + handleResult: function AsyncAddonListCallback_handleResult(aResults) { + let row = null; + while ((row = aResults.getNextRow())) { + this.count++; + let self = this; + XPIDatabase.makeAddonFromRowAsync(row, function handleResult_makeAddonFromRowAsync(aAddon) { + function completeAddon(aRepositoryAddon) { + aAddon._repositoryAddon = aRepositoryAddon; + aAddon.compatibilityOverrides = aRepositoryAddon ? + aRepositoryAddon.compatibilityOverrides : + null; + self.addons.push(aAddon); + if (self.complete && self.addons.length == self.count) + self.callback(self.addons); + } + + if ("getCachedAddonByID" in AddonRepository) + AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); + else + completeAddon(null); + }); + } + }, + + handleError: asyncErrorLogger, + + handleCompletion: function AsyncAddonListCallback_handleCompletion(aReason) { + this.complete = true; + if (this.addons.length == this.count) + this.callback(this.addons); + } +}; + /** * Asynchronously fill in the _repositoryAddon field for one addon */ @@ -232,6 +293,24 @@ function asyncErrorLogger(aError) { logSQLError(aError.result, aError.message); } +/** + * A helper function to execute a statement synchronously and log any error + * that occurs. + * + * @param aStatement + * A mozIStorageStatement to execute + */ +function executeStatement(aStatement) { + try { + aStatement.execute(); + } + catch (e) { + logSQLError(XPIDatabase.connection.lastError, + XPIDatabase.connection.lastErrorString); + throw e; + } +} + /** * A helper function to step a statement synchronously and log any error that * occurs. @@ -293,6 +372,12 @@ function copyRowProperties(aRow, aProperties, aTarget) { return aTarget; } +/** + * Create a DBAddonInternal from the fields saved in the JSON database + * or loaded into an AddonInternal from an XPI manifest. + * @return a DBAddonInternal populated with the loaded data + */ + /** * The DBAddonInternal is a special AddonInternal that has been retrieved from * the database. The constructor will initialize the DBAddonInternal with a set @@ -304,7 +389,6 @@ function copyRowProperties(aRow, aProperties, aTarget) { */ function DBAddonInternal(aLoaded) { copyProperties(aLoaded, PROP_JSON_FIELDS, this); - if (aLoaded._installLocation) { this._installLocation = aLoaded._installLocation; this.location = aLoaded._installLocation._name; @@ -312,9 +396,7 @@ function DBAddonInternal(aLoaded) { else if (aLoaded.location) { this._installLocation = XPIProvider.installLocationsByName[this.location]; } - this._key = this.location + ":" + this.id; - try { this._sourceBundle = this._installLocation.getLocationForID(this.id); } @@ -324,20 +406,20 @@ function DBAddonInternal(aLoaded) { // this change is being detected. } - // XXX Can we redesign pendingUpgrade? - XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", - function DBA_pendingUpgradeGetter() { + Object.defineProperty(this, "pendingUpgrade", { + get: function DBA_pendingUpgradeGetter() { + delete this.pendingUpgrade; for (let install of XPIProvider.installs) { if (install.state == AddonManager.STATE_INSTALLED && !(install.addon.inDatabase) && install.addon.id == this.id && install.installLocation == this._installLocation) { - delete this.pendingUpgrade; return this.pendingUpgrade = install.addon; } }; - return null; - }); + }, + configurable: true + }); } DBAddonInternal.prototype = { @@ -355,13 +437,8 @@ DBAddonInternal.prototype = { XPIProvider.updateAddonDisabledState(this); XPIDatabase.commitTransaction(); }, - get inDatabase() { return true; - }, - - toJSON: function() { - return copyProperties(this, PROP_JSON_FIELDS); } } @@ -370,21 +447,94 @@ DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; this.XPIDatabase = { // true if the database connection has been opened initialized: false, + // A cache of statements that are used and need to be finalized on shutdown + statementCache: {}, + // A cache of weak referenced DBAddonInternals so we can reuse objects where + // possible + addonCache: [], // The nested transaction count transactionCount: 0, // The database file + dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true), jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), // Migration data loaded from an old version of the database. migrateData: null, // Active add-on directories loaded from extensions.ini and prefs at startup. activeBundles: null, - // Special handling for when the database is locked at first load - lockedDatabase: false, - // XXX may be able to refactor this away + // The statements used by the database + statements: { + _getDefaultLocale: "SELECT id, name, description, creator, homepageURL " + + "FROM locale WHERE id=:id", + _getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " + + "locale.description, locale.creator, locale.homepageURL " + + "FROM addon_locale JOIN locale ON " + + "addon_locale.locale_id=locale.id WHERE " + + "addon_internal_id=:internal_id", + _getTargetApplications: "SELECT addon_internal_id, id, minVersion, " + + "maxVersion FROM targetApplication WHERE " + + "addon_internal_id=:internal_id", + _getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " + + "addon_internal_id=:internal_id", + _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " + + "WHERE locale_id=:id", + + clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id", + updateAddonActive: "UPDATE addon SET active=:active WHERE " + + "internal_id=:internal_id", + + getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " + + "type<>'theme' AND bootstrap=0", + getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + + "internalName=:internalName AND type='theme'", + getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'", + + getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " + + "AND location=:location", + getAddons: "SELECT " + FIELDS_ADDON + " FROM addon", + getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type", + getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + + "location=:location", + getInstallLocations: "SELECT DISTINCT location FROM addon", + getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + + "visible=1 AND id=:id", + getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " + + "WHERE visible=1 AND internalName=:internalName", + getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1", + getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " + + "addon WHERE visible=1 " + + "AND (pendingUninstall=1 OR " + + "MAX(userDisabled,appDisabled)=active)", + getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " + + "WHERE syncGUID=:syncGUID", + makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id", + removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id", + // Equates to active = visible && !userDisabled && !softDisabled && + // !appDisabled && !pendingUninstall + setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " + + "1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)", + setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " + + "appDisabled=:appDisabled, " + + "softDisabled=:softDisabled, " + + "pendingUninstall=:pendingUninstall, " + + "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " + + "internal_id=:internal_id", + setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " + + "internal_id=:internal_id", + setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " + + "internal_id=:internal_id", + updateTargetApplications: "UPDATE targetApplication SET " + + "minVersion=:minVersion, maxVersion=:maxVersion " + + "WHERE addon_internal_id=:internal_id AND id=:id", + + createSavepoint: "SAVEPOINT 'default'", + releaseSavepoint: "RELEASE SAVEPOINT 'default'", + rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'" + }, + get dbfileExists() { delete this.dbfileExists; - return this.dbfileExists = this.jsonFile.exists(); + return this.dbfileExists = this.dbfile.exists(); }, set dbfileExists(aValue) { delete this.dbfileExists; @@ -395,19 +545,12 @@ this.XPIDatabase = { * Converts the current internal state of the XPI addon database to JSON * and writes it to the user's profile. Synchronous for now, eventually must * be async, reliable, etc. - * XXX should we remove the JSON file if it would be empty? Not sure if that - * would ever happen, given the default theme */ writeJSON: function XPIDB_writeJSON() { // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet - - // Don't mess with an existing database on disk, if it was locked at start up - if (this.lockedDatabase) - return; - let addons = []; - for (let [key, addon] of this.addonDB) { - addons.push(addon); + for (let aKey in this.addonDB) { + addons.push(copyProperties(this.addonDB[aKey], PROP_JSON_FIELDS)); } let toSave = { schemaVersion: DB_SCHEMA, @@ -420,17 +563,11 @@ this.XPIDatabase = { try { converter.init(stream, "UTF-8", 0, 0x0000); // XXX pretty print the JSON while debugging - let out = JSON.stringify(toSave, null, 2); - // dump("Writing JSON:\n" + out + "\n"); - converter.writeString(out); + converter.writeString(JSON.stringify(toSave, null, 2)); converter.flush(); // nsConverterOutputStream doesn't finish() safe output streams on close() FileUtils.closeSafeFileOutputStream(stream); converter.close(); - this.dbfileExists = true; - // XXX probably only want to do this if the version is different - Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); - Services.prefs.savePrefFile(null); // XXX is this bad sync I/O? } catch(e) { ERROR("Failed to save database to JSON", e); @@ -438,6 +575,66 @@ this.XPIDatabase = { } }, + /** + * Open and parse the JSON XPI extensions database. + * @return true: the DB was successfully loaded + * false: The DB either needs upgrade or did not exist at all. + * XXX upgrade and errors handled in a following patch + */ + openJSONDatabase: function XPIDB_openJSONDatabase() { + dump("XPIDB_openJSONDatabase\n"); + try { + let data = ""; + let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + fstream.init(this.jsonFile, -1, 0, 0); + cstream.init(fstream, "UTF-8", 0, 0); + let (str = {}) { + let read = 0; + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + } + cstream.close(); + let inputAddons = JSON.parse(data); + // Now do some sanity checks on our JSON db + if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { + // XXX Content of JSON file is bad, need to rebuild from scratch + ERROR("bad JSON file contents"); + delete this.addonDB; + this.addonDB = {}; + return false; + } + if (inputAddons.schemaVersion != DB_SCHEMA) { + // XXX UPGRADE FROM PREVIOUS VERSION OF JSON DB + ERROR("JSON schema upgrade needed"); + return false; + } + // If we got here, we probably have good data + // Make AddonInternal instances from the loaded data and save them + delete this.addonDB; + let addonDB = {} + inputAddons.addons.forEach(function(loadedAddon) { + let newAddon = new DBAddonInternal(loadedAddon); + addonDB[newAddon._key] = newAddon; + }); + this.addonDB = addonDB; + // dump("Finished reading DB: " + this.addonDB.toSource() + "\n"); + return true; + } + catch(e) { + // XXX handle missing JSON database + ERROR("Failed to load XPI JSON data from profile", e); + // XXX for now, start from scratch + delete this.addonDB; + this.addonDB = {}; + return false; + } + }, + /** * Begins a new transaction in the database. Transactions may be nested. Data * written by an inner transaction may be rolled back on its own. Rolling back @@ -484,197 +681,170 @@ this.XPIDatabase = { }, /** - * Pull upgrade information from an existing SQLITE database + * Attempts to open the database file. If it fails it will try to delete the + * existing file and create an empty database. If that fails then it will + * open an in-memory database that can be used during this session. * - * @return false if there is no SQLITE database - * true and sets this.migrateData to null if the SQLITE DB exists - * but does not contain useful information - * true and sets this.migrateData to - * {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...} - * if there is useful information + * @param aDBFile + * The nsIFile to open + * @return the mozIStorageConnection for the database */ - loadSqliteData: function XPIDB_loadSqliteData() { + openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) { + LOG("Opening database"); let connection = null; - let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); - if (!dbfile.exists()) { - return false; - } + // Attempt to open the database try { - connection = Services.storage.openUnsharedDatabase(dbfile); + connection = Services.storage.openUnsharedDatabase(aDBFile); + this.dbfileExists = true; } catch (e) { - // exists but SQLITE can't open it - WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e); - this.migrateData = null; - return true; + ERROR("Failed to open database (1st attempt)", e); + // If the database was locked for some reason then assume it still + // has some good data and we should try to load it the next time around. + if (e.result != Cr.NS_ERROR_STORAGE_BUSY) { + try { + aDBFile.remove(true); + } + catch (e) { + ERROR("Failed to remove database that could not be opened", e); + } + try { + connection = Services.storage.openUnsharedDatabase(aDBFile); + } + catch (e) { + ERROR("Failed to open database (2nd attempt)", e); + + // If we have got here there seems to be no way to open the real + // database, instead open a temporary memory database so things will + // work for this session. + return Services.storage.openSpecialDatabase("memory"); + } + } + else { + return Services.storage.openSpecialDatabase("memory"); + } } - LOG("Migrating data from sqlite"); - this.migrateData = this.getMigrateDataFromDatabase(connection); - connection.close(); - return true; + + connection.executeSimpleSQL("PRAGMA synchronous = FULL"); + connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); + + return connection; }, /** - * Opens and reads the database file, upgrading from old - * databases or making a new DB if needed. + * Opens a new connection to the database file. * - * The possibilities, in order of priority, are: - * 1) Perfectly good, up to date database - * 2) Out of date JSON database needs to be upgraded => upgrade - * 3) JSON database exists but is mangled somehow => build new JSON - * 4) no JSON DB, but a useable SQLITE db we can upgrade from => upgrade - * 5) useless SQLITE DB => build new JSON - * 6) useable RDF DB => upgrade - * 7) useless RDF DB => build new JSON - * 8) Nothing at all => build new JSON * @param aRebuildOnError * A boolean indicating whether add-on information should be loaded * from the install locations if the database needs to be rebuilt. - * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) { - // XXX TELEMETRY report opens with aRebuildOnError true (which implies delayed open) - // vs. aRebuildOnError false (DB loaded during startup) - delete this.addonDB; - this.migrateData = null; - let fstream = null; - let data = ""; - try { - LOG("Opening XPI database " + this.jsonFile.path); - fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - fstream.init(this.jsonFile, -1, 0, 0); - let cstream = null; - try { - cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - cstream.init(fstream, "UTF-8", 0, 0); - let (str = {}) { - let read = 0; - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - data += str.value; - } while (read != 0); - } - // dump("Loaded JSON:\n" + data + "\n"); - let inputAddons = JSON.parse(data); - // Now do some sanity checks on our JSON db - if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { - // Content of JSON file is bad, need to rebuild from scratch - ERROR("bad JSON file contents"); - this.rebuildDatabase(aRebuildOnError); - } - if (inputAddons.schemaVersion != DB_SCHEMA) { - // Handle mismatched JSON schema version. For now, we assume backward/forward - // compatibility as long as we preserve unknown fields during save & restore - // XXX preserve schema version and unknown fields during save/restore - LOG("JSON schema mismatch: expected " + DB_SCHEMA + - ", actual " + inputAddons.schemaVersion); - } - // If we got here, we probably have good data - // Make AddonInternal instances from the loaded data and save them - let addonDB = new Map(); - inputAddons.addons.forEach(function(loadedAddon) { - let newAddon = new DBAddonInternal(loadedAddon); - addonDB.set(newAddon._key, newAddon); - }); - this.addonDB = addonDB; - LOG("Successfully read XPI database"); - } - catch(e) { - // If we catch and log a SyntaxError from the JSON - // parser, the xpcshell test harness fails the test for us: bug 870828 - if (e.name == "SyntaxError") { - ERROR("Syntax error parsing saved XPI JSON data"); - } - else { - ERROR("Failed to load XPI JSON data from profile", e); - } - this.rebuildDatabase(aRebuildOnError); - } - finally { - if (cstream) - cstream.close(); - } - } - catch (e) { - if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { - // XXX re-implement logic to decide whether to upgrade database - // by checking the DB_SCHEMA_VERSION preference. - // Fall back to attempting database upgrades - WARN("Extensions database not found; attempting to upgrade"); - // See if there is SQLITE to migrate from - if (!this.loadSqliteData()) { - // Nope, try RDF - this.migrateData = this.getMigrateDataFromRDF(); - } - - this.rebuildDatabase(aRebuildOnError); - } - else { - WARN("Extensions database " + this.jsonFile.path + - " exists but is not readable; rebuilding in memory", e); - // XXX open question - if we can overwrite at save time, should we, or should we - // leave the locked database in case we can recover from it next time we start up? - this.lockedDatabase = true; - // XXX TELEMETRY report when this happens? - this.rebuildDatabase(aRebuildOnError); - } - } - finally { - if (fstream) - fstream.close(); - } - + this.openJSONDatabase(); this.initialized = true; return; + // XXX IRVING deal with the migration logic below and in openDatabaseFile... + + delete this.connection; - // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"? if (!aForceOpen && !this.dbfileExists) { this.connection = null; return; } - }, - /** - * Rebuild the database from addon install directories. If this.migrateData - * is available, uses migrated information for settings on the addons found - * during rebuild - * @param aRebuildOnError - * A boolean indicating whether add-on information should be loaded - * from the install locations if the database needs to be rebuilt. - * (if false, caller is XPIProvider.checkForChanges() which will rebuild) - */ - rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) { - // If there is no migration data then load the list of add-on directories - // that were active during the last run - this.addonDB = new Map(); - if (!this.migrateData) - this.activeBundles = this.getActiveBundles(); + this.migrateData = null; - if (aRebuildOnError) { - WARN("Rebuilding add-ons database from installed extensions."); - this.beginTransaction(); + this.connection = this.openDatabaseFile(this.dbfile); + + // If the database was corrupt or missing then the new blank database will + // have a schema version of 0. + let schemaVersion = this.connection.schemaVersion; + if (schemaVersion != DB_SCHEMA) { + // A non-zero schema version means that a schema has been successfully + // created in the database in the past so we might be able to get useful + // information from it + if (schemaVersion != 0) { + LOG("Migrating data from schema " + schemaVersion); + this.migrateData = this.getMigrateDataFromDatabase(); + + // Delete the existing database + this.connection.close(); + try { + if (this.dbfileExists) + this.dbfile.remove(true); + + // Reopen an empty database + this.connection = this.openDatabaseFile(this.dbfile); + } + catch (e) { + ERROR("Failed to remove old database", e); + // If the file couldn't be deleted then fall back to an in-memory + // database + this.connection = Services.storage.openSpecialDatabase("memory"); + } + } + else { + let dbSchema = 0; + try { + dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA); + } catch (e) {} + + if (dbSchema == 0) { + // Only migrate data from the RDF if we haven't done it before + this.migrateData = this.getMigrateDataFromRDF(); + } + } + + // At this point the database should be completely empty try { - let state = XPIProvider.getInstallLocationStates(); - XPIProvider.processFileChanges(state, {}, false); - // Make sure to update the active add-ons and add-ons list on shutdown - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - this.commitTransaction(); + this.createSchema(); } catch (e) { - ERROR("Error processing file changes", e); - this.rollbackTransaction(); + // If creating the schema fails, then the database is unusable, + // fall back to an in-memory database. + this.connection = Services.storage.openSpecialDatabase("memory"); + } + + // If there is no migration data then load the list of add-on directories + // that were active during the last run + if (!this.migrateData) + this.activeBundles = this.getActiveBundles(); + + if (aRebuildOnError) { + WARN("Rebuilding add-ons database from installed extensions."); + this.beginTransaction(); + try { + let state = XPIProvider.getInstallLocationStates(); + XPIProvider.processFileChanges(state, {}, false); + // Make sure to update the active add-ons and add-ons list on shutdown + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + this.commitTransaction(); + } + catch (e) { + ERROR("Error processing file changes", e); + this.rollbackTransaction(); + } } } + + // If the database connection has a file open then it has the right schema + // by now so make sure the preferences reflect that. + if (this.connection.databaseFile) { + Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); + Services.prefs.savePrefFile(null); + } + + // Begin any pending transactions + for (let i = 0; i < this.transactionCount; i++) + this.connection.executeSimpleSQL("SAVEPOINT 'default'"); }, /** * Lazy getter for the addons database */ get addonDB() { - this.openConnection(true); + delete this.addonDB; + this.openJSONDatabase(); return this.addonDB; }, @@ -794,13 +964,13 @@ this.XPIDatabase = { * @return an object holding information about what add-ons were previously * userDisabled and any updated compatibility information */ - getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase(aConnection) { + getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase() { let migrateData = {}; // Attempt to migrate data from a different (even future!) version of the // database try { - var stmt = aConnection.createStatement("PRAGMA table_info(addon)"); + var stmt = this.connection.createStatement("PRAGMA table_info(addon)"); const REQUIRED = ["internal_id", "id", "location", "userDisabled", "installDate", "version"]; @@ -826,7 +996,7 @@ this.XPIDatabase = { } stmt.finalize(); - stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon"); + stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon"); for (let row in resultRows(stmt)) { if (!(row.location in migrateData)) migrateData[row.location] = {}; @@ -845,7 +1015,7 @@ this.XPIDatabase = { }) } - var taStmt = aConnection.createStatement("SELECT id, minVersion, " + + var taStmt = this.connection.createStatement("SELECT id, minVersion, " + "maxVersion FROM " + "targetApplication WHERE " + "addon_internal_id=:internal_id"); @@ -893,8 +1063,10 @@ this.XPIDatabase = { // If we are running with an in-memory database then force a new // extensions.ini to be written to disk on the next startup - if (this.lockedDatabase) - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + // XXX IRVING special case for if we fail to save extensions.json? + // XXX maybe doesn't need to be at shutdown? + // if (!this.connection.databaseFile) + // Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); this.initialized = false; @@ -903,7 +1075,7 @@ this.XPIDatabase = { delete this.addonDB; Object.defineProperty(this, "addonDB", { get: function addonsGetter() { - this.openConnection(true); + this.openJSONDatabase(); return this.addonDB; }, configurable: true @@ -926,17 +1098,17 @@ this.XPIDatabase = { * installed add-ons, occasionally a superset when an install location no * longer exists. * - * @return a Set of names of install locations + * @return an array of names of install locations */ getInstallLocations: function XPIDB_getInstallLocations() { - let locations = new Set(); if (!this.addonDB) - return locations; + return []; - for (let [, addon] of this.addonDB) { - locations.add(addon.location); + let locations = {}; + for each (let addon in this.addonDB) { + locations[addon.location] = 1; } - return locations; + return Object.keys(locations); }, /** @@ -951,7 +1123,8 @@ this.XPIDatabase = { return []; let addonList = []; - for (let [key, addon] of this.addonDB) { + for (let key in this.addonDB) { + let addon = this.addonDB[key]; if (aFilter(addon)) { addonList.push(addon); } @@ -971,7 +1144,8 @@ this.XPIDatabase = { if (!this.addonDB) return null; - for (let [key, addon] of this.addonDB) { + for (let key in this.addonDB) { + let addon = this.addonDB[key]; if (aFilter(addon)) { return addon; } @@ -1004,7 +1178,7 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { - getRepositoryAddon(this.addonDB.get(aLocation + ":" + aId), aCallback); + getRepositoryAddon(this.addonDB[aLocation + ":" + aId], aCallback); }, /** @@ -1131,7 +1305,7 @@ this.XPIDatabase = { let newAddon = new DBAddonInternal(aAddon); newAddon.descriptor = aDescriptor; - this.addonDB.set(newAddon._key, newAddon); + this.addonDB[newAddon._key] = newAddon; if (newAddon.visible) { this.makeAddonVisible(newAddon); } @@ -1184,7 +1358,7 @@ this.XPIDatabase = { */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { this.beginTransaction(); - this.addonDB.delete(aAddon._key); + delete this.addonDB[aAddon._key]; this.commitTransaction(); }, @@ -1200,7 +1374,8 @@ this.XPIDatabase = { makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { this.beginTransaction(); LOG("Make addon " + aAddon._key + " visible"); - for (let [key, otherAddon] of this.addonDB) { + for (let key in this.addonDB) { + let otherAddon = this.addonDB[key]; if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { LOG("Hide addon " + otherAddon._key); otherAddon.visible = false; @@ -1286,20 +1461,14 @@ this.XPIDatabase = { // XXX IRVING this may get called during XPI-utils shutdown // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); - let changed = false; - for (let [key, addon] of this.addonDB) { - let newActive = (addon.visible && !addon.userDisabled && + this.beginTransaction(); + for (let key in this.addonDB) { + let addon = this.addonDB[key]; + addon.active = (addon.visible && !addon.userDisabled && !addon.softDisabled && !addon.appDisabled && !addon.pendingUninstall); - if (newActive != addon.active) { - addon.active = newActive; - changed = true; - } - } - if (changed) { - this.beginTransaction(); - this.commitTransaction(); } + this.commitTransaction(); }, /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 781f38e21c30..e0c34ee1aa35 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1395,16 +1395,16 @@ function do_exception_wrap(func) { } const EXTENSIONS_DB = "extensions.json"; -let gExtensionsJSON = gProfD.clone(); -gExtensionsJSON.append(EXTENSIONS_DB); /** * Change the schema version of the JSON extensions database */ function changeXPIDBVersion(aNewVersion) { - let jData = loadJSON(gExtensionsJSON); + let dbfile = gProfD.clone(); + dbfile.append(EXTENSIONS_DB); + let jData = loadJSON(dbfile); jData.schemaVersion = aNewVersion; - saveJSON(jData, gExtensionsJSON); + saveJSON(jData, dbfile); } /** @@ -1426,7 +1426,7 @@ function loadJSON(aFile) { } while (read != 0); } cstream.close(); - do_print("Loaded JSON file " + aFile.path); + do_print("Loaded JSON file " + aFile.spec); return(JSON.parse(data)); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js b/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js deleted file mode 100644 index d3ccf68f3a0d..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js +++ /dev/null @@ -1,54 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// Tests that we rebuild the database correctly if it contains -// JSON data that parses correctly but doesn't contain required fields - -var addon1 = { - id: "addon1@tests.mozilla.org", - version: "2.0", - name: "Test 1", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }] -}; - -const profileDir = gProfD.clone(); -profileDir.append("extensions"); - -function run_test() { - do_test_pending("Bad JSON"); - - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - - // This addon will be auto-installed at startup - writeInstallRDFForExtension(addon1, profileDir); - - startupManager(); - - shutdownManager(); - - // First startup/shutdown finished - // Replace the JSON store with something bogus - saveJSON({not: "what we expect to find"}, gExtensionsJSON); - - startupManager(false); - // Retrieve an addon to force the database to rebuild - AddonManager.getAddonsByIDs([addon1.id], callback_soon(after_db_rebuild)); -} - -function after_db_rebuild([a1]) { - do_check_eq(a1.id, addon1.id); - - shutdownManager(); - - // Make sure our JSON database has schemaVersion and our installed extension - let data = loadJSON(gExtensionsJSON); - do_check_true("schemaVersion" in data); - do_check_eq(data.addons[0].id, addon1.id); - - do_test_finished("Bad JSON"); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index 9a72b50c6733..6d201640f989 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -146,9 +146,10 @@ function run_test() { startupManager(); - do_check_false(gExtensionsJSON.exists()); - let file = gProfD.clone(); + file.append(EXTENSIONS_DB); + do_check_false(file.exists()); + file.leafName = "extensions.ini"; do_check_false(file.exists()); @@ -204,9 +205,10 @@ function run_test_1() { } function check_test_1(installSyncGUID) { - do_check_true(gExtensionsJSON.exists()); - let file = gProfD.clone(); + file.append(EXTENSIONS_DB); + do_check_true(file.exists()); + file.leafName = "extensions.ini"; do_check_false(file.exists()); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index af1c845f19ff..789819cc6b87 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -47,7 +47,9 @@ function run_test_1() { shutdownManager(); - gExtensionsJSON.remove(true); + let db = gProfD.clone(); + db.append(EXTENSIONS_DB); + db.remove(true); do_execute_soon(check_test_1); }); @@ -60,8 +62,10 @@ function check_test_1() { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); - do_check_true(gExtensionsJSON.exists()); - do_check_true(gExtensionsJSON.fileSize > 0); + let db = gProfD.clone(); + db.append(EXTENSIONS_DB); + do_check_true(db.exists()); + do_check_true(db.fileSize > 0); end_test(); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js index 4c8b3750d695..50dd782da360 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -251,8 +251,10 @@ function run_test_1() { // serves this purpose). On startup the add-ons manager won't rebuild // because there is a file there still. shutdownManager(); - gExtensionsJSON.remove(true); - gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + var dbfile = gProfD.clone(); + dbfile.append("extensions.json"); + dbfile.remove(true); + dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); // Accessing the add-ons should open and recover the database diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js index 3ba6d213beb0..e465b47bbacb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js @@ -252,8 +252,10 @@ function run_test_1() { // serves this purpose). On startup the add-ons manager won't rebuild // because there is a file there still. shutdownManager(); - gExtensionsJSON.remove(true); - gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + var dbfile = gProfD.clone(); + dbfile.append("extensions.json"); + dbfile.remove(true); + dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); // Accessing the add-ons should open and recover the database diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js index 91d09c07c7e2..883ea08c6d1f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -257,8 +257,10 @@ function run_test_1() { // After shutting down the database won't be open so we can // mess with permissions shutdownManager(); - var savedPermissions = gExtensionsJSON.permissions; - gExtensionsJSON.permissions = 0; + var dbfile = gProfD.clone(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -426,12 +428,11 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); + dbfile.permissions = savedPermissions; + // After allowing access to the original DB things should go back to as // they were previously - shutdownManager(); - gExtensionsJSON.permissions = savedPermissions; - startupManager(); - + restartManager(); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 79c3439fd4e8..6e21df540d1f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -144,8 +144,10 @@ function run_test() { // After shutting down the database won't be open so we can lock it shutdownManager(); - var savedPermissions = gExtensionsJSON.permissions; - gExtensionsJSON.permissions = 0; + var dbfile = gProfD.clone(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -197,11 +199,11 @@ function run_test() { do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a6.id)); + dbfile.permissions = savedPermissions; + // After allowing access to the original DB things should still be // applied correctly - shutdownManager(); - gExtensionsJSON.permissions = savedPermissions; - startupManager(); + restartManager(); // These things happened when we had no access to the database so // they are seen as external changes when we get the database back :( diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index f38549cdaae2..2c31171318be 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -256,8 +256,10 @@ function run_test_1() { // After shutting down the database won't be open so we can lock it shutdownManager(); - var savedPermissions = gExtensionsJSON.permissions; - gExtensionsJSON.permissions = 0; + var dbfile = gProfD.clone(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -423,11 +425,11 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); + dbfile.permissions = savedPermissions; + // After allowing access to the original DB things should go back to as // they were previously - shutdownManager(); - gExtensionsJSON.permissions = savedPermissions; - startupManager(false); + restartManager(); // Shouldn't have seen any startup changes check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 6113454b47a2..49fef590e1c0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -16,9 +16,12 @@ skip-if = os == "android" [test_DeferredSave.js] [test_LightweightThemeManager.js] [test_backgroundupdate.js] -[test_bad_json.js] [test_badschema.js] +# Needs rewrite for JSON XPIDB +fail-if = true [test_blocklistchange.js] +# Needs rewrite for JSON XPIDB +fail-if = true # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_blocklist_regexp.js] @@ -139,6 +142,8 @@ fail-if = os == "android" [test_bug620837.js] [test_bug655254.js] [test_bug659772.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_bug675371.js] [test_bug740612.js] [test_bug753900.js] @@ -148,7 +153,11 @@ fail-if = os == "android" [test_ChromeManifestParser.js] [test_compatoverrides.js] [test_corrupt.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_corrupt_strictcompat.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_dictionary.js] [test_langpack.js] [test_disable.js] @@ -193,17 +202,33 @@ skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. [test_locale.js] [test_locked.js] +# Needs sqlite->JSON conversion +fail-if = true [test_locked2.js] +# Needs sqlite->JSON conversion +fail-if = true [test_locked_strictcompat.js] +# Needs sqlite->JSON conversion +fail-if = true [test_manifest.js] [test_mapURIToAddonID.js] # Same as test_bootstrap.js skip-if = os == "android" [test_migrate1.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate2.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate3.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate4.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate5.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrateAddonRepository.js] [test_onPropertyChanged_appDisabled.js] [test_permissions.js] From cf05e6d90d06bb2a02abf550732a8179178e51d6 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:20:08 +0200 Subject: [PATCH 30/40] Backed out changeset b3afaf3b4f3a (bug 853388) --- toolkit/mozapps/extensions/XPIProvider.jsm | 310 ++-- .../mozapps/extensions/XPIProviderUtils.js | 1276 +++++++++++------ .../extensions/test/xpcshell/head_addons.js | 54 - .../test/xpcshell/test_badschema.js | 13 +- .../test/xpcshell/test_blocklistchange.js | 26 +- .../test/xpcshell/test_bootstrap.js | 2 + .../test/xpcshell/test_bug559800.js | 2 + .../test/xpcshell/test_bug659772.js | 16 +- .../extensions/test/xpcshell/test_corrupt.js | 2 +- .../xpcshell/test_corrupt_strictcompat.js | 2 +- .../test/xpcshell/test_db_sanity.js | 181 +++ .../extensions/test/xpcshell/test_locked.js | 15 +- .../extensions/test/xpcshell/test_locked2.js | 12 +- .../test/xpcshell/test_locked_strictcompat.js | 12 +- .../extensions/test/xpcshell/test_migrate2.js | 2 +- .../extensions/test/xpcshell/test_migrate4.js | 12 +- .../extensions/test/xpcshell/test_migrate5.js | 2 +- .../extensions/test/xpcshell/test_startup.js | 4 +- .../extensions/test/xpcshell/test_syncGUID.js | 3 +- .../extensions/test/xpcshell/xpcshell.ini | 27 +- 20 files changed, 1287 insertions(+), 686 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index ca763ec78741..b29db7b388f0 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -78,7 +78,7 @@ const DIR_STAGE = "staged"; const DIR_XPI_STAGE = "staged-xpis"; const DIR_TRASH = "trash"; -const FILE_DATABASE = "extensions.json"; +const FILE_DATABASE = "extensions.sqlite"; const FILE_OLD_CACHE = "extensions.cache"; const FILE_INSTALL_MANIFEST = "install.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; @@ -120,12 +120,7 @@ const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; // or calculated const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled", "sourceURI", "applyBackgroundUpdates", - "releaseNotesURI", "foreignInstall", "syncGUID"]; -// Properties to cache and reload when an addon installation is pending -const PENDING_INSTALL_METADATA = - ["syncGUID", "targetApplications", "userDisabled", "softDisabled", - "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", - "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]; + "releaseNotesURI", "isForeignInstall", "syncGUID"]; // Note: When adding/changing/removing items here, remember to change the // DB schema version to ensure changes are picked up ASAP. @@ -174,15 +169,12 @@ var gGlobalScope = this; var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i; ["LOG", "WARN", "ERROR"].forEach(function(aName) { - Object.defineProperty(this, aName, { - get: function logFuncGetter() { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + this.__defineGetter__(aName, function logFuncGetter() { + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); - LogManager.getLogger("addons.xpi", this); - return this[aName]; - }, - configurable: true - }); + LogManager.getLogger("addons.xpi", this); + return this[aName]; + }) }, this); @@ -205,12 +197,9 @@ function loadLazyObjects() { } for (let name of LAZY_OBJECTS) { - Object.defineProperty(gGlobalScope, name, { - get: function lazyObjectGetter() { - let objs = loadLazyObjects(); - return objs[name]; - }, - configurable: true + gGlobalScope.__defineGetter__(name, function lazyObjectGetter() { + let objs = loadLazyObjects(); + return objs[name]; }); } @@ -595,13 +584,10 @@ function isAddonDisabled(aAddon) { return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled; } -Object.defineProperty(this, "gRDF", { - get: function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); - }, - configurable: true +this.__defineGetter__("gRDF", function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); }); function EM_R(aProperty) { @@ -708,11 +694,8 @@ function loadManifestFromRDF(aUri, aStream) { }); PROP_LOCALE_MULTI.forEach(function(aProp) { - // Don't store empty arrays - let props = getPropertyArray(aDs, aSource, - aProp.substring(0, aProp.length - 1)); - if (props.length > 0) - locale[aProp] = props; + locale[aProp] = getPropertyArray(aDs, aSource, + aProp.substring(0, aProp.length - 1)); }); return locale; @@ -2535,33 +2518,31 @@ var XPIProvider = { newAddon.visible = !(newAddon.id in visibleAddons); // Update the database - let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, - aAddonState.descriptor); - if (newDBAddon.visible) { - visibleAddons[newDBAddon.id] = newDBAddon; + XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor); + if (newAddon.visible) { + visibleAddons[newAddon.id] = newAddon; // Remember add-ons that were changed during startup AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newDBAddon.id); + newAddon.id); // If this was the active theme and it is now disabled then enable the // default theme - if (aOldAddon.active && isAddonDisabled(newDBAddon)) + if (aOldAddon.active && isAddonDisabled(newAddon)) XPIProvider.enableDefaultTheme(); // If the new add-on is bootstrapped and active then call its install method - if (newDBAddon.active && newDBAddon.bootstrap) { + if (newAddon.active && newAddon.bootstrap) { // Startup cache must be flushed before calling the bootstrap script flushStartupCache(); - let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ? + let installReason = Services.vc.compare(aOldAddon.version, newAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, - newDBAddon.type, file, "install", - installReason, { oldVersion: aOldAddon.version }); + XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, + "install", installReason, { oldVersion: aOldAddon.version }); return false; } @@ -2588,7 +2569,7 @@ var XPIProvider = { function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) { LOG("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor); - aOldAddon.descriptor = aAddonState.descriptor; + aOldAddon._descriptor = aAddonState.descriptor; aOldAddon.visible = !(aOldAddon.id in visibleAddons); // Update the database @@ -2649,7 +2630,8 @@ var XPIProvider = { // If it should be active then mark it as active otherwise unload // its scope if (!isAddonDisabled(aOldAddon)) { - XPIDatabase.updateAddonActive(aOldAddon, true); + aOldAddon.active = true; + XPIDatabase.updateAddonActive(aOldAddon); } else { XPIProvider.unloadBootstrapScope(newAddon.id); @@ -2708,7 +2690,8 @@ var XPIProvider = { AddonManagerPrivate.addStartupChange(change, aOldAddon.id); if (aOldAddon.bootstrap) { // Update the add-ons active state - XPIDatabase.updateAddonActive(aOldAddon, !isDisabled); + aOldAddon.active = !isDisabled; + XPIDatabase.updateAddonActive(aOldAddon); } else { changed = true; @@ -2730,15 +2713,17 @@ var XPIProvider = { /** * Called when an add-on has been removed. * + * @param aInstallLocation + * The install location containing the add-on * @param aOldAddon * The AddonInternal as it appeared the last time the application * ran * @return a boolean indicating if flushing caches is required to complete * changing this add-on */ - function removeMetadata(aOldAddon) { + function removeMetadata(aInstallLocation, aOldAddon) { // This add-on has disappeared - LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location); + LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation); XPIDatabase.removeAddonMetadata(aOldAddon); // Remember add-ons that were uninstalled during startup @@ -2901,10 +2886,9 @@ var XPIProvider = { newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon)) } - let newDBAddon = null; try { // Update the database. - newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); + XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); } catch (e) { // Failing to write the add-on into the database is non-fatal, the @@ -2915,36 +2899,36 @@ var XPIProvider = { return false; } - if (newDBAddon.visible) { + if (newAddon.visible) { // Remember add-ons that were first detected during startup. if (isDetectedInstall) { // If a copy from a higher priority location was removed then this // add-on has changed if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED) - .indexOf(newDBAddon.id) != -1) { + .indexOf(newAddon.id) != -1) { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newDBAddon.id); + newAddon.id); } else { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, - newDBAddon.id); + newAddon.id); } } // Note if any visible add-on is not in the application install location - if (newDBAddon._installLocation.name != KEY_APP_GLOBAL) + if (newAddon._installLocation.name != KEY_APP_GLOBAL) XPIProvider.allAppGlobal = false; - visibleAddons[newDBAddon.id] = newDBAddon; + visibleAddons[newAddon.id] = newAddon; let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL; let extraParams = {}; // If we're hiding a bootstrapped add-on then call its uninstall method - if (newDBAddon.id in oldBootstrappedAddons) { - let oldBootstrap = oldBootstrappedAddons[newDBAddon.id]; + if (newAddon.id in oldBootstrappedAddons) { + let oldBootstrap = oldBootstrappedAddons[newAddon.id]; extraParams.oldVersion = oldBootstrap.version; - XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap; + XPIProvider.bootstrappedAddons[newAddon.id] = oldBootstrap; // If the old version is the same as the new version, or we're // recovering from a corrupt DB, don't call uninstall and install @@ -2952,7 +2936,7 @@ var XPIProvider = { if (sameVersion || !isNewInstall) return false; - installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ? + installReason = Services.vc.compare(oldBootstrap.version, newAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; @@ -2960,27 +2944,27 @@ var XPIProvider = { createInstance(Ci.nsIFile); oldAddonFile.persistentDescriptor = oldBootstrap.descriptor; - XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version, + XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version, oldBootstrap.type, oldAddonFile, "uninstall", - installReason, { newVersion: newDBAddon.version }); - XPIProvider.unloadBootstrapScope(newDBAddon.id); + installReason, { newVersion: newAddon.version }); + XPIProvider.unloadBootstrapScope(newAddon.id); // If the new add-on is bootstrapped then we must flush the caches // before calling the new bootstrap script - if (newDBAddon.bootstrap) + if (newAddon.bootstrap) flushStartupCache(); } - if (!newDBAddon.bootstrap) + if (!newAddon.bootstrap) return true; // Visible bootstrapped add-ons need to have their install method called let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, newDBAddon.type, file, + XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, "install", installReason, extraParams); - if (!newDBAddon.active) - XPIProvider.unloadBootstrapScope(newDBAddon.id); + if (!newAddon.active) + XPIProvider.unloadBootstrapScope(newAddon.id); } return false; @@ -3050,7 +3034,7 @@ var XPIProvider = { changed = updateMetadata(installLocation, aOldAddon, addonState) || changed; } - else if (aOldAddon.descriptor != addonState.descriptor) { + else if (aOldAddon._descriptor != addonState.descriptor) { changed = updateDescriptor(installLocation, aOldAddon, addonState) || changed; } @@ -3063,7 +3047,7 @@ var XPIProvider = { XPIProvider.allAppGlobal = false; } else { - changed = removeMetadata(aOldAddon) || changed; + changed = removeMetadata(installLocation.name, aOldAddon) || changed; } }, this); } @@ -3087,7 +3071,7 @@ var XPIProvider = { knownLocations.forEach(function(aLocation) { let addons = XPIDatabase.getAddonsInLocation(aLocation); addons.forEach(function(aOldAddon) { - changed = removeMetadata(aOldAddon) || changed; + changed = removeMetadata(aLocation, aOldAddon) || changed; }, this); }, this); @@ -3481,7 +3465,7 @@ var XPIProvider = { let results = [createWrapper(a) for each (a in aAddons)]; XPIProvider.installs.forEach(function(aInstall) { if (aInstall.state == AddonManager.STATE_INSTALLED && - !(aInstall.addon.inDatabase)) + !(aInstall.addon instanceof DBAddonInternal)) results.push(createWrapper(aInstall.addon)); }); aCallback(results); @@ -3799,7 +3783,7 @@ var XPIProvider = { // This wouldn't normally be called for an already installed add-on (except // for forming the operationsRequiringRestart flags) so is really here as // a safety measure. - if (aAddon.inDatabase) + if (aAddon instanceof DBAddonInternal) return false; // If we have an AddonInstall for this add-on then we can see if there is @@ -4048,7 +4032,7 @@ var XPIProvider = { updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled) { - if (!(aAddon.inDatabase)) + if (!(aAddon instanceof DBAddonInternal)) throw new Error("Can only update addon states for installed addons."); if (aUserDisabled !== undefined && aSoftDisabled !== undefined) { throw new Error("Cannot change userDisabled and softDisabled at the " + @@ -4121,7 +4105,8 @@ var XPIProvider = { } if (!needsRestart) { - XPIDatabase.updateAddonActive(aAddon, !isDisabled); + aAddon.active = !isDisabled; + XPIDatabase.updateAddonActive(aAddon); if (isDisabled) { if (aAddon.bootstrap) { let file = aAddon._installLocation.getLocationForID(aAddon.id); @@ -4157,7 +4142,7 @@ var XPIProvider = { * location that does not allow it */ uninstallAddon: function XPI_uninstallAddon(aAddon) { - if (!(aAddon.inDatabase)) + if (!(aAddon instanceof DBAddonInternal)) throw new Error("Can only uninstall installed addons."); if (aAddon._installLocation.locked) @@ -4199,7 +4184,8 @@ var XPIProvider = { AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false); if (!isAddonDisabled(aAddon) && !XPIProvider.enableRequiresRestart(aAddon)) { - XPIDatabase.updateAddonActive(aAddon, true); + aAddon.active = true; + XPIDatabase.updateAddonActive(aAddon); } if (aAddon.bootstrap) { @@ -4269,7 +4255,7 @@ var XPIProvider = { * The DBAddonInternal to cancel uninstall for */ cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) { - if (!(aAddon.inDatabase)) + if (!(aAddon instanceof DBAddonInternal)) throw new Error("Can only cancel uninstall for installed addons."); cleanStagingDir(aAddon._installLocation.getStagingDir(), [aAddon.id]); @@ -5258,7 +5244,7 @@ AddonInstall.prototype = { // Point the add-on to its extracted files as the xpi may get deleted this.addon._sourceBundle = stagedAddon; - // Cache the AddonInternal as it may have updated compatibility info + // Cache the AddonInternal as it may have updated compatibiltiy info let stagedJSON = stagedAddon.clone(); stagedJSON.leafName = this.addon.id + ".json"; if (stagedJSON.exists()) @@ -5327,7 +5313,8 @@ AddonInstall.prototype = { } if (!isUpgrade && this.existingAddon.active) { - XPIDatabase.updateAddonActive(this.existingAddon, false); + this.existingAddon.active = false; + XPIDatabase.updateAddonActive(this.existingAddon); } } @@ -5343,45 +5330,51 @@ AddonInstall.prototype = { this.addon.updateDate = recursiveLastModifiedTime(file); this.addon.visible = true; if (isUpgrade) { - this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, - file.persistentDescriptor); + XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, + file.persistentDescriptor); } else { this.addon.installDate = this.addon.updateDate; this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon)) - this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); + XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); } - let extraParams = {}; - if (this.existingAddon) { - extraParams.oldVersion = this.existingAddon.version; - } + // Retrieve the new DBAddonInternal for the add-on we just added + let self = this; + XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name, + function startInstall_getAddonInLocation(a) { + self.addon = a; + let extraParams = {}; + if (self.existingAddon) { + extraParams.oldVersion = self.existingAddon.version; + } - if (this.addon.bootstrap) { - XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, - this.addon.type, file, "install", - reason, extraParams); - } - - AddonManagerPrivate.callAddonListeners("onInstalled", - createWrapper(this.addon)); - - LOG("Install of " + this.sourceURI.spec + " completed."); - this.state = AddonManager.STATE_INSTALLED; - AddonManagerPrivate.callInstallListeners("onInstallEnded", - this.listeners, this.wrapper, - createWrapper(this.addon)); - - if (this.addon.bootstrap) { - if (this.addon.active) { - XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, - this.addon.type, file, "startup", + if (self.addon.bootstrap) { + XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, + self.addon.type, file, "install", reason, extraParams); } - else { - XPIProvider.unloadBootstrapScope(this.addon.id); + + AddonManagerPrivate.callAddonListeners("onInstalled", + createWrapper(self.addon)); + + LOG("Install of " + self.sourceURI.spec + " completed."); + self.state = AddonManager.STATE_INSTALLED; + AddonManagerPrivate.callInstallListeners("onInstallEnded", + self.listeners, self.wrapper, + createWrapper(self.addon)); + + if (self.addon.bootstrap) { + if (self.addon.active) { + XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, + self.addon.type, file, "startup", + reason, extraParams); + } + else { + XPIProvider.unloadBootstrapScope(self.addon.id); + } } - } + }); } } catch (e) { @@ -5773,6 +5766,13 @@ AddonInternal.prototype = { releaseNotesURI: null, foreignInstall: false, + get isForeignInstall() { + return this.foreignInstall; + }, + set isForeignInstall(aVal) { + this.foreignInstall = aVal; + }, + get selectedLocale() { if (this._selectedLocale) return this._selectedLocale; @@ -5967,7 +5967,10 @@ AddonInternal.prototype = { * A JS object containing the cached metadata */ importMetadata: function AddonInternal_importMetaData(aObj) { - PENDING_INSTALL_METADATA.forEach(function(aProp) { + ["syncGUID", "targetApplications", "userDisabled", "softDisabled", + "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", + "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"] + .forEach(function(aProp) { if (!(aProp in aObj)) return; @@ -5979,6 +5982,77 @@ AddonInternal.prototype = { } }; +/** + * The DBAddonInternal is a special AddonInternal that has been retrieved from + * the database. Add-ons retrieved synchronously only have the basic metadata + * the rest is filled out synchronously when needed. Asynchronously read add-ons + * have all data available. + */ +function DBAddonInternal() { + this.__defineGetter__("targetApplications", function DBA_targetApplicationsGetter() { + delete this.targetApplications; + return this.targetApplications = XPIDatabase._getTargetApplications(this); + }); + + this.__defineGetter__("targetPlatforms", function DBA_targetPlatformsGetter() { + delete this.targetPlatforms; + return this.targetPlatforms = XPIDatabase._getTargetPlatforms(this); + }); + + this.__defineGetter__("locales", function DBA_localesGetter() { + delete this.locales; + return this.locales = XPIDatabase._getLocales(this); + }); + + this.__defineGetter__("defaultLocale", function DBA_defaultLocaleGetter() { + delete this.defaultLocale; + return this.defaultLocale = XPIDatabase._getDefaultLocale(this); + }); + + this.__defineGetter__("pendingUpgrade", function DBA_pendingUpgradeGetter() { + delete this.pendingUpgrade; + for (let install of XPIProvider.installs) { + if (install.state == AddonManager.STATE_INSTALLED && + !(install.addon instanceof DBAddonInternal) && + install.addon.id == this.id && + install.installLocation == this._installLocation) { + return this.pendingUpgrade = install.addon; + } + }; + }); +} + +DBAddonInternal.prototype = { + applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { + let changes = []; + this.targetApplications.forEach(function(aTargetApp) { + aUpdate.targetApplications.forEach(function(aUpdateTarget) { + if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || + Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { + aTargetApp.minVersion = aUpdateTarget.minVersion; + aTargetApp.maxVersion = aUpdateTarget.maxVersion; + changes.push(aUpdateTarget); + } + }); + }); + try { + XPIDatabase.updateTargetApplications(this, changes); + } + catch (e) { + // A failure just means that we discard the compatibility update + ERROR("Failed to update target application info in the database for " + + "add-on " + this.id, e); + return; + } + XPIProvider.updateAddonDisabledState(this); + } +} + +DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; +// Make it accessible to XPIDatabase. +XPIProvider.DBAddonInternal = DBAddonInternal; + + /** * Creates an AddonWrapper for an AddonInternal. * @@ -6230,7 +6304,7 @@ function AddonWrapper(aAddon) { if (aAddon.syncGUID == val) return val; - if (aAddon.inDatabase) + if (aAddon instanceof DBAddonInternal) XPIDatabase.setAddonSyncGUID(aAddon, val); aAddon.syncGUID = val; @@ -6257,7 +6331,7 @@ function AddonWrapper(aAddon) { this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() { let pending = 0; - if (!(aAddon.inDatabase)) { + if (!(aAddon instanceof DBAddonInternal)) { // Add-on is pending install if there is no associated install (shouldn't // happen here) or if the install is in the process of or has successfully // completed the install. If an add-on is pending install then we ignore @@ -6301,7 +6375,7 @@ function AddonWrapper(aAddon) { let permissions = 0; // Add-ons that aren't installed cannot be modified in any way - if (!(aAddon.inDatabase)) + if (!(aAddon instanceof DBAddonInternal)) return permissions; if (!aAddon.appDisabled) { @@ -6336,7 +6410,7 @@ function AddonWrapper(aAddon) { if (val == this.userDisabled) return val; - if (aAddon.inDatabase) { + if (aAddon instanceof DBAddonInternal) { if (aAddon.type == "theme" && val) { if (aAddon.internalName == XPIProvider.defaultSkin) throw new Error("Cannot disable the default theme"); @@ -6360,7 +6434,7 @@ function AddonWrapper(aAddon) { if (val == aAddon.softDisabled) return val; - if (aAddon.inDatabase) { + if (aAddon instanceof DBAddonInternal) { // When softDisabling a theme just enable the active theme if (aAddon.type == "theme" && val && !aAddon.userDisabled) { if (aAddon.internalName == XPIProvider.defaultSkin) @@ -6385,7 +6459,7 @@ function AddonWrapper(aAddon) { }; this.uninstall = function AddonWrapper_uninstall() { - if (!(aAddon.inDatabase)) + if (!(aAddon instanceof DBAddonInternal)) throw new Error("Cannot uninstall an add-on that isn't installed"); if (aAddon.pendingUninstall) throw new Error("Add-on is already marked to be uninstalled"); @@ -6393,7 +6467,7 @@ function AddonWrapper(aAddon) { }; this.cancelUninstall = function AddonWrapper_cancelUninstall() { - if (!(aAddon.inDatabase)) + if (!(aAddon instanceof DBAddonInternal)) throw new Error("Cannot cancel uninstall for an add-on that isn't installed"); if (!aAddon.pendingUninstall) throw new Error("Add-on is not marked to be uninstalled"); diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 465b663058c5..0bdcab9af571 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -18,21 +18,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", ["LOG", "WARN", "ERROR"].forEach(function(aName) { - Object.defineProperty(this, aName, { - get: function logFuncGetter () { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + this.__defineGetter__(aName, function logFuncGetter () { + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); - LogManager.getLogger("addons.xpi-utils", this); - return this[aName]; - }, - configurable: true - }); + LogManager.getLogger("addons.xpi-utils", this); + return this[aName]; + }) }, this); const KEY_PROFILEDIR = "ProfD"; const FILE_DATABASE = "extensions.sqlite"; -const FILE_JSON_DB = "extensions.json"; const FILE_OLD_DATABASE = "extensions.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; @@ -76,30 +72,16 @@ const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"]; const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; -// Properties to save in JSON file -const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", - "internalName", "updateURL", "updateKey", "optionsURL", - "optionsType", "aboutURL", "iconURL", "icon64URL", - "defaultLocale", "visible", "active", "userDisabled", - "appDisabled", "pendingUninstall", "descriptor", "installDate", - "updateDate", "applyBackgroundUpdates", "bootstrap", - "skinnable", "size", "sourceURI", "releaseNotesURI", - "softDisabled", "foreignInstall", "hasBinaryComponents", - "strictCompatibility", "locales", "targetApplications", - "targetPlatforms"]; const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; -Object.defineProperty(this, "gRDF", { - get: function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); - }, - configurable: true +this.__defineGetter__("gRDF", function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); }); function EM_R(aProperty) { @@ -192,67 +174,6 @@ AsyncAddonListCallback.prototype = { } }; -/** - * Asynchronously fill in the _repositoryAddon field for one addon - */ -function getRepositoryAddon(aAddon, aCallback) { - if (!aAddon) { - aCallback(aAddon); - return; - } - function completeAddon(aRepositoryAddon) { - aAddon._repositoryAddon = aRepositoryAddon; - aAddon.compatibilityOverrides = aRepositoryAddon ? - aRepositoryAddon.compatibilityOverrides : - null; - aCallback(aAddon); - } - AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); -} - -/** - * A helper method to asynchronously call a function on an array - * of objects, calling a callback when function(x) has been gathered - * for every element of the array. - * WARNING: not currently error-safe; if the async function does not call - * our internal callback for any of the array elements, asyncMap will not - * call the callback parameter. - * - * @param aObjects - * The array of objects to process asynchronously - * @param aMethod - * Function with signature function(object, function aCallback(f_of_object)) - * @param aCallback - * Function with signature f([aMethod(object)]), called when all values - * are available - */ -function asyncMap(aObjects, aMethod, aCallback) { - var resultsPending = aObjects.length; - var results = [] - if (resultsPending == 0) { - aCallback(results); - return; - } - - function asyncMap_gotValue(aIndex, aValue) { - results[aIndex] = aValue; - if (--resultsPending == 0) { - aCallback(results); - } - } - - aObjects.map(function asyncMap_each(aObject, aIndex, aArray) { - try { - aMethod(aObject, function asyncMap_callback(aResult) { - asyncMap_gotValue(aIndex, aResult); - }); - } - catch (e) { - WARN("Async map function failed", e); - asyncMap_gotValue(aIndex, undefined); - } - }); -} /** * A generator to synchronously return result rows from an mozIStorageStatement. @@ -372,78 +293,6 @@ function copyRowProperties(aRow, aProperties, aTarget) { return aTarget; } -/** - * Create a DBAddonInternal from the fields saved in the JSON database - * or loaded into an AddonInternal from an XPI manifest. - * @return a DBAddonInternal populated with the loaded data - */ - -/** - * The DBAddonInternal is a special AddonInternal that has been retrieved from - * the database. The constructor will initialize the DBAddonInternal with a set - * of fields, which could come from either the JSON store or as an - * XPIProvider.AddonInternal created from an addon's manifest - * @constructor - * @param aLoaded - * Addon data fields loaded from JSON or the addon manifest. - */ -function DBAddonInternal(aLoaded) { - copyProperties(aLoaded, PROP_JSON_FIELDS, this); - if (aLoaded._installLocation) { - this._installLocation = aLoaded._installLocation; - this.location = aLoaded._installLocation._name; - } - else if (aLoaded.location) { - this._installLocation = XPIProvider.installLocationsByName[this.location]; - } - this._key = this.location + ":" + this.id; - try { - this._sourceBundle = this._installLocation.getLocationForID(this.id); - } - catch (e) { - // An exception will be thrown if the add-on appears in the database but - // not on disk. In general this should only happen during startup as - // this change is being detected. - } - - Object.defineProperty(this, "pendingUpgrade", { - get: function DBA_pendingUpgradeGetter() { - delete this.pendingUpgrade; - for (let install of XPIProvider.installs) { - if (install.state == AddonManager.STATE_INSTALLED && - !(install.addon.inDatabase) && - install.addon.id == this.id && - install.installLocation == this._installLocation) { - return this.pendingUpgrade = install.addon; - } - }; - }, - configurable: true - }); -} - -DBAddonInternal.prototype = { - applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { - XPIDatabase.beginTransaction(); - this.targetApplications.forEach(function(aTargetApp) { - aUpdate.targetApplications.forEach(function(aUpdateTarget) { - if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || - Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { - aTargetApp.minVersion = aUpdateTarget.minVersion; - aTargetApp.maxVersion = aUpdateTarget.maxVersion; - } - }); - }); - XPIProvider.updateAddonDisabledState(this); - XPIDatabase.commitTransaction(); - }, - get inDatabase() { - return true; - } -} - -DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; - this.XPIDatabase = { // true if the database connection has been opened initialized: false, @@ -456,7 +305,6 @@ this.XPIDatabase = { transactionCount: 0, // The database file dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true), - jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), // Migration data loaded from an old version of the database. migrateData: null, // Active add-on directories loaded from extensions.ini and prefs at startup. @@ -479,6 +327,30 @@ this.XPIDatabase = { _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " + "WHERE locale_id=:id", + addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " + + ":location, :version, :type, :internalName, " + + ":updateURL, :updateKey, :optionsURL, " + + ":optionsType, :aboutURL, " + + ":iconURL, :icon64URL, :locale, :visible, :active, " + + ":userDisabled, :appDisabled, :pendingUninstall, " + + ":descriptor, :installDate, :updateDate, " + + ":applyBackgroundUpdates, :bootstrap, :skinnable, " + + ":size, :sourceURI, :releaseNotesURI, :softDisabled, " + + ":isForeignInstall, :hasBinaryComponents, " + + ":strictCompatibility)", + addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " + + "(:internal_id, :name, :locale)", + addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " + + "homepageURL) VALUES (:name, :description, " + + ":creator, :homepageURL)", + addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " + + ":type, :value)", + addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " + + "(:internal_id, :id, :minVersion, " + + ":maxVersion)", + addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " + + "(:internal_id, :os, :abi)", + clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id", updateAddonActive: "UPDATE addon SET active=:active WHERE " + "internal_id=:internal_id", @@ -541,100 +413,6 @@ this.XPIDatabase = { return this.dbfileExists = aValue; }, - /** - * Converts the current internal state of the XPI addon database to JSON - * and writes it to the user's profile. Synchronous for now, eventually must - * be async, reliable, etc. - */ - writeJSON: function XPIDB_writeJSON() { - // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet - let addons = []; - for (let aKey in this.addonDB) { - addons.push(copyProperties(this.addonDB[aKey], PROP_JSON_FIELDS)); - } - let toSave = { - schemaVersion: DB_SCHEMA, - addons: addons - }; - - let stream = FileUtils.openSafeFileOutputStream(this.jsonFile); - let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. - createInstance(Ci.nsIConverterOutputStream); - try { - converter.init(stream, "UTF-8", 0, 0x0000); - // XXX pretty print the JSON while debugging - converter.writeString(JSON.stringify(toSave, null, 2)); - converter.flush(); - // nsConverterOutputStream doesn't finish() safe output streams on close() - FileUtils.closeSafeFileOutputStream(stream); - converter.close(); - } - catch(e) { - ERROR("Failed to save database to JSON", e); - stream.close(); - } - }, - - /** - * Open and parse the JSON XPI extensions database. - * @return true: the DB was successfully loaded - * false: The DB either needs upgrade or did not exist at all. - * XXX upgrade and errors handled in a following patch - */ - openJSONDatabase: function XPIDB_openJSONDatabase() { - dump("XPIDB_openJSONDatabase\n"); - try { - let data = ""; - let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - fstream.init(this.jsonFile, -1, 0, 0); - cstream.init(fstream, "UTF-8", 0, 0); - let (str = {}) { - let read = 0; - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - data += str.value; - } while (read != 0); - } - cstream.close(); - let inputAddons = JSON.parse(data); - // Now do some sanity checks on our JSON db - if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { - // XXX Content of JSON file is bad, need to rebuild from scratch - ERROR("bad JSON file contents"); - delete this.addonDB; - this.addonDB = {}; - return false; - } - if (inputAddons.schemaVersion != DB_SCHEMA) { - // XXX UPGRADE FROM PREVIOUS VERSION OF JSON DB - ERROR("JSON schema upgrade needed"); - return false; - } - // If we got here, we probably have good data - // Make AddonInternal instances from the loaded data and save them - delete this.addonDB; - let addonDB = {} - inputAddons.addons.forEach(function(loadedAddon) { - let newAddon = new DBAddonInternal(loadedAddon); - addonDB[newAddon._key] = newAddon; - }); - this.addonDB = addonDB; - // dump("Finished reading DB: " + this.addonDB.toSource() + "\n"); - return true; - } - catch(e) { - // XXX handle missing JSON database - ERROR("Failed to load XPI JSON data from profile", e); - // XXX for now, start from scratch - delete this.addonDB; - this.addonDB = {}; - return false; - } - }, - /** * Begins a new transaction in the database. Transactions may be nested. Data * written by an inner transaction may be rolled back on its own. Rolling back @@ -645,6 +423,8 @@ this.XPIDatabase = { * when the database is first opened. */ beginTransaction: function XPIDB_beginTransaction() { + if (this.initialized) + this.getStatement("createSavepoint").execute(); this.transactionCount++; }, @@ -658,12 +438,9 @@ this.XPIDatabase = { return; } + if (this.initialized) + this.getStatement("releaseSavepoint").execute(); this.transactionCount--; - - if (this.transactionCount == 0) { - // All our nested transactions are done, write the JSON file - this.writeJSON(); - } }, /** @@ -676,8 +453,11 @@ this.XPIDatabase = { return; } + if (this.initialized) { + this.getStatement("rollbackSavepoint").execute(); + this.getStatement("releaseSavepoint").execute(); + } this.transactionCount--; - // XXX IRVING we don't handle rollback in the JSON store }, /** @@ -714,7 +494,7 @@ this.XPIDatabase = { } catch (e) { ERROR("Failed to open database (2nd attempt)", e); - + // If we have got here there seems to be no way to open the real // database, instead open a temporary memory database so things will // work for this session. @@ -738,20 +518,18 @@ this.XPIDatabase = { * @param aRebuildOnError * A boolean indicating whether add-on information should be loaded * from the install locations if the database needs to be rebuilt. + * @return the migration data from the database if it was an old schema or + * null otherwise. */ openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) { - this.openJSONDatabase(); - this.initialized = true; - return; - // XXX IRVING deal with the migration logic below and in openDatabaseFile... - delete this.connection; if (!aForceOpen && !this.dbfileExists) { this.connection = null; - return; + return {}; } + this.initialized = true; this.migrateData = null; this.connection = this.openDatabaseFile(this.dbfile); @@ -840,12 +618,11 @@ this.XPIDatabase = { }, /** - * Lazy getter for the addons database + * A lazy getter for the database connection. */ - get addonDB() { - delete this.addonDB; - this.openJSONDatabase(); - return this.addonDB; + get connection() { + this.openConnection(true); + return this.connection; }, /** @@ -1006,8 +783,6 @@ this.XPIDatabase = { migrateData[row.location][row.id] = addonData; props.forEach(function(aProp) { - if (aProp == "isForeignInstall") - addonData.foreignInstall = (row[aProp] == 1); if (DB_BOOL_METADATA.indexOf(aProp) != -1) addonData[aProp] = row[aProp] == 1; else @@ -1055,6 +830,11 @@ this.XPIDatabase = { shutdown: function XPIDB_shutdown(aCallback) { LOG("shutdown"); if (this.initialized) { + for each (let stmt in this.statementCache) + stmt.finalize(); + this.statementCache = {}; + this.addonCache = []; + if (this.transactionCount > 0) { ERROR(this.transactionCount + " outstanding transactions, rolling back."); while (this.transactionCount > 0) @@ -1063,28 +843,24 @@ this.XPIDatabase = { // If we are running with an in-memory database then force a new // extensions.ini to be written to disk on the next startup - // XXX IRVING special case for if we fail to save extensions.json? - // XXX maybe doesn't need to be at shutdown? - // if (!this.connection.databaseFile) - // Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + if (!this.connection.databaseFile) + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); this.initialized = false; + let connection = this.connection; + delete this.connection; - // Clear out the cached addons data loaded from JSON and recreate - // the getter to allow database re-loads during testing. - delete this.addonDB; - Object.defineProperty(this, "addonDB", { - get: function addonsGetter() { - this.openJSONDatabase(); - return this.addonDB; - }, - configurable: true + // Re-create the connection smart getter to allow the database to be + // re-loaded during testing. + this.__defineGetter__("connection", function connectionGetter() { + this.openConnection(true); + return this.connection; }); - // XXX IRVING removed an async callback when the database was closed - // XXX do we want to keep the ability to async flush extensions.json - // XXX and then call back? - if (aCallback) + + connection.asyncClose(function shutdown_asyncClose() { + LOG("Database closed"); aCallback(); + }); } else { if (aCallback) @@ -1093,7 +869,433 @@ this.XPIDatabase = { }, /** - * Return a list of all install locations known about by the database. This + * Gets a cached statement or creates a new statement if it doesn't already + * exist. + * + * @param key + * A unique key to reference the statement + * @param aSql + * An optional SQL string to use for the query, otherwise a + * predefined sql string for the key will be used. + * @return a mozIStorageStatement for the passed SQL + */ + getStatement: function XPIDB_getStatement(aKey, aSql) { + if (aKey in this.statementCache) + return this.statementCache[aKey]; + if (!aSql) + aSql = this.statements[aKey]; + + try { + return this.statementCache[aKey] = this.connection.createStatement(aSql); + } + catch (e) { + ERROR("Error creating statement " + aKey + " (" + aSql + ")"); + throw e; + } + }, + + /** + * Creates the schema in the database. + */ + createSchema: function XPIDB_createSchema() { + LOG("Creating database schema"); + this.beginTransaction(); + + // Any errors in here should rollback the transaction + try { + this.connection.createTable("addon", + "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "id TEXT, syncGUID TEXT, " + + "location TEXT, version TEXT, " + + "type TEXT, internalName TEXT, updateURL TEXT, " + + "updateKey TEXT, optionsURL TEXT, " + + "optionsType TEXT, aboutURL TEXT, iconURL TEXT, " + + "icon64URL TEXT, defaultLocale INTEGER, " + + "visible INTEGER, active INTEGER, " + + "userDisabled INTEGER, appDisabled INTEGER, " + + "pendingUninstall INTEGER, descriptor TEXT, " + + "installDate INTEGER, updateDate INTEGER, " + + "applyBackgroundUpdates INTEGER, " + + "bootstrap INTEGER, skinnable INTEGER, " + + "size INTEGER, sourceURI TEXT, " + + "releaseNotesURI TEXT, softDisabled INTEGER, " + + "isForeignInstall INTEGER, " + + "hasBinaryComponents INTEGER, " + + "strictCompatibility INTEGER, " + + "UNIQUE (id, location), " + + "UNIQUE (syncGUID)"); + this.connection.createTable("targetApplication", + "addon_internal_id INTEGER, " + + "id TEXT, minVersion TEXT, maxVersion TEXT, " + + "UNIQUE (addon_internal_id, id)"); + this.connection.createTable("targetPlatform", + "addon_internal_id INTEGER, " + + "os, abi TEXT, " + + "UNIQUE (addon_internal_id, os, abi)"); + this.connection.createTable("addon_locale", + "addon_internal_id INTEGER, "+ + "locale TEXT, locale_id INTEGER, " + + "UNIQUE (addon_internal_id, locale)"); + this.connection.createTable("locale", + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "name TEXT, description TEXT, creator TEXT, " + + "homepageURL TEXT"); + this.connection.createTable("locale_strings", + "locale_id INTEGER, type TEXT, value TEXT"); + this.connection.executeSimpleSQL("CREATE INDEX locale_strings_idx ON " + + "locale_strings (locale_id)"); + this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " + + "ON addon BEGIN " + + "DELETE FROM targetApplication WHERE addon_internal_id=old.internal_id; " + + "DELETE FROM targetPlatform WHERE addon_internal_id=old.internal_id; " + + "DELETE FROM addon_locale WHERE addon_internal_id=old.internal_id; " + + "DELETE FROM locale WHERE id=old.defaultLocale; " + + "END"); + this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon_locale AFTER " + + "DELETE ON addon_locale WHEN NOT EXISTS " + + "(SELECT * FROM addon_locale WHERE locale_id=old.locale_id) BEGIN " + + "DELETE FROM locale WHERE id=old.locale_id; " + + "END"); + this.connection.executeSimpleSQL("CREATE TRIGGER delete_locale AFTER " + + "DELETE ON locale BEGIN " + + "DELETE FROM locale_strings WHERE locale_id=old.id; " + + "END"); + this.connection.schemaVersion = DB_SCHEMA; + this.commitTransaction(); + } + catch (e) { + ERROR("Failed to create database schema", e); + logSQLError(this.connection.lastError, this.connection.lastErrorString); + this.rollbackTransaction(); + this.connection.close(); + this.connection = null; + throw e; + } + }, + + /** + * Synchronously reads the multi-value locale strings for a locale + * + * @param aLocale + * The locale object to read into + */ + _readLocaleStrings: function XPIDB__readLocaleStrings(aLocale) { + let stmt = this.getStatement("_readLocaleStrings"); + + stmt.params.id = aLocale.id; + for (let row in resultRows(stmt)) { + if (!(row.type in aLocale)) + aLocale[row.type] = []; + aLocale[row.type].push(row.value); + } + }, + + /** + * Synchronously reads the locales for an add-on + * + * @param aAddon + * The DBAddonInternal to read the locales for + * @return the array of locales + */ + _getLocales: function XPIDB__getLocales(aAddon) { + let stmt = this.getStatement("_getLocales"); + + let locales = []; + stmt.params.internal_id = aAddon._internal_id; + for (let row in resultRows(stmt)) { + let locale = { + id: row.id, + locales: [row.locale] + }; + copyProperties(row, PROP_LOCALE_SINGLE, locale); + locales.push(locale); + } + locales.forEach(function(aLocale) { + this._readLocaleStrings(aLocale); + }, this); + return locales; + }, + + /** + * Synchronously reads the default locale for an add-on + * + * @param aAddon + * The DBAddonInternal to read the default locale for + * @return the default locale for the add-on + * @throws if the database does not contain the default locale information + */ + _getDefaultLocale: function XPIDB__getDefaultLocale(aAddon) { + let stmt = this.getStatement("_getDefaultLocale"); + + stmt.params.id = aAddon._defaultLocale; + if (!stepStatement(stmt)) + throw new Error("Missing default locale for " + aAddon.id); + let locale = copyProperties(stmt.row, PROP_LOCALE_SINGLE); + locale.id = aAddon._defaultLocale; + stmt.reset(); + this._readLocaleStrings(locale); + return locale; + }, + + /** + * Synchronously reads the target application entries for an add-on + * + * @param aAddon + * The DBAddonInternal to read the target applications for + * @return an array of target applications + */ + _getTargetApplications: function XPIDB__getTargetApplications(aAddon) { + let stmt = this.getStatement("_getTargetApplications"); + + stmt.params.internal_id = aAddon._internal_id; + return [copyProperties(row, PROP_TARGETAPP) for each (row in resultRows(stmt))]; + }, + + /** + * Synchronously reads the target platform entries for an add-on + * + * @param aAddon + * The DBAddonInternal to read the target platforms for + * @return an array of target platforms + */ + _getTargetPlatforms: function XPIDB__getTargetPlatforms(aAddon) { + let stmt = this.getStatement("_getTargetPlatforms"); + + stmt.params.internal_id = aAddon._internal_id; + return [copyProperties(row, ["os", "abi"]) for each (row in resultRows(stmt))]; + }, + + /** + * Synchronously makes a DBAddonInternal from a storage row or returns one + * from the cache. + * + * @param aRow + * The storage row to make the DBAddonInternal from + * @return a DBAddonInternal + */ + makeAddonFromRow: function XPIDB_makeAddonFromRow(aRow) { + if (this.addonCache[aRow.internal_id]) { + let addon = this.addonCache[aRow.internal_id].get(); + if (addon) + return addon; + } + + let addon = new XPIProvider.DBAddonInternal(); + addon._internal_id = aRow.internal_id; + addon._installLocation = XPIProvider.installLocationsByName[aRow.location]; + addon._descriptor = aRow.descriptor; + addon._defaultLocale = aRow.defaultLocale; + copyProperties(aRow, PROP_METADATA, addon); + copyProperties(aRow, DB_METADATA, addon); + DB_BOOL_METADATA.forEach(function(aProp) { + addon[aProp] = aRow[aProp] != 0; + }); + try { + addon._sourceBundle = addon._installLocation.getLocationForID(addon.id); + } + catch (e) { + // An exception will be thrown if the add-on appears in the database but + // not on disk. In general this should only happen during startup as + // this change is being detected. + } + + this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon); + return addon; + }, + + /** + * Asynchronously fetches additional metadata for a DBAddonInternal. + * + * @param aAddon + * The DBAddonInternal + * @param aCallback + * The callback to call when the metadata is completely retrieved + */ + fetchAddonMetadata: function XPIDB_fetchAddonMetadata(aAddon) { + function readLocaleStrings(aLocale, aCallback) { + let stmt = XPIDatabase.getStatement("_readLocaleStrings"); + + stmt.params.id = aLocale.id; + stmt.executeAsync({ + handleResult: function readLocaleStrings_handleResult(aResults) { + let row = null; + while ((row = aResults.getNextRow())) { + let type = row.getResultByName("type"); + if (!(type in aLocale)) + aLocale[type] = []; + aLocale[type].push(row.getResultByName("value")); + } + }, + + handleError: asyncErrorLogger, + + handleCompletion: function readLocaleStrings_handleCompletion(aReason) { + aCallback(); + } + }); + } + + function readDefaultLocale() { + delete aAddon.defaultLocale; + let stmt = XPIDatabase.getStatement("_getDefaultLocale"); + + stmt.params.id = aAddon._defaultLocale; + stmt.executeAsync({ + handleResult: function readDefaultLocale_handleResult(aResults) { + aAddon.defaultLocale = copyRowProperties(aResults.getNextRow(), + PROP_LOCALE_SINGLE); + aAddon.defaultLocale.id = aAddon._defaultLocale; + }, + + handleError: asyncErrorLogger, + + handleCompletion: function readDefaultLocale_handleCompletion(aReason) { + if (aAddon.defaultLocale) { + readLocaleStrings(aAddon.defaultLocale, readLocales); + } + else { + ERROR("Missing default locale for " + aAddon.id); + readLocales(); + } + } + }); + } + + function readLocales() { + delete aAddon.locales; + aAddon.locales = []; + let stmt = XPIDatabase.getStatement("_getLocales"); + + stmt.params.internal_id = aAddon._internal_id; + stmt.executeAsync({ + handleResult: function readLocales_handleResult(aResults) { + let row = null; + while ((row = aResults.getNextRow())) { + let locale = { + id: row.getResultByName("id"), + locales: [row.getResultByName("locale")] + }; + copyRowProperties(row, PROP_LOCALE_SINGLE, locale); + aAddon.locales.push(locale); + } + }, + + handleError: asyncErrorLogger, + + handleCompletion: function readLocales_handleCompletion(aReason) { + let pos = 0; + function readNextLocale() { + if (pos < aAddon.locales.length) + readLocaleStrings(aAddon.locales[pos++], readNextLocale); + else + readTargetApplications(); + } + + readNextLocale(); + } + }); + } + + function readTargetApplications() { + delete aAddon.targetApplications; + aAddon.targetApplications = []; + let stmt = XPIDatabase.getStatement("_getTargetApplications"); + + stmt.params.internal_id = aAddon._internal_id; + stmt.executeAsync({ + handleResult: function readTargetApplications_handleResult(aResults) { + let row = null; + while ((row = aResults.getNextRow())) + aAddon.targetApplications.push(copyRowProperties(row, PROP_TARGETAPP)); + }, + + handleError: asyncErrorLogger, + + handleCompletion: function readTargetApplications_handleCompletion(aReason) { + readTargetPlatforms(); + } + }); + } + + function readTargetPlatforms() { + delete aAddon.targetPlatforms; + aAddon.targetPlatforms = []; + let stmt = XPIDatabase.getStatement("_getTargetPlatforms"); + + stmt.params.internal_id = aAddon._internal_id; + stmt.executeAsync({ + handleResult: function readTargetPlatforms_handleResult(aResults) { + let row = null; + while ((row = aResults.getNextRow())) + aAddon.targetPlatforms.push(copyRowProperties(row, ["os", "abi"])); + }, + + handleError: asyncErrorLogger, + + handleCompletion: function readTargetPlatforms_handleCompletion(aReason) { + let callbacks = aAddon._pendingCallbacks; + delete aAddon._pendingCallbacks; + callbacks.forEach(function(aCallback) { + aCallback(aAddon); + }); + } + }); + } + + readDefaultLocale(); + }, + + /** + * Synchronously makes a DBAddonInternal from a mozIStorageRow or returns one + * from the cache. + * + * @param aRow + * The mozIStorageRow to make the DBAddonInternal from + * @return a DBAddonInternal + */ + makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) { + let internal_id = aRow.getResultByName("internal_id"); + if (this.addonCache[internal_id]) { + let addon = this.addonCache[internal_id].get(); + if (addon) { + // If metadata is still pending for this instance add our callback to + // the list to be called when complete, otherwise pass the addon to + // our callback + if ("_pendingCallbacks" in addon) + addon._pendingCallbacks.push(aCallback); + else + aCallback(addon); + return; + } + } + + let addon = new XPIProvider.DBAddonInternal(); + addon._internal_id = internal_id; + let location = aRow.getResultByName("location"); + addon._installLocation = XPIProvider.installLocationsByName[location]; + addon._descriptor = aRow.getResultByName("descriptor"); + copyRowProperties(aRow, PROP_METADATA, addon); + addon._defaultLocale = aRow.getResultByName("defaultLocale"); + copyRowProperties(aRow, DB_METADATA, addon); + DB_BOOL_METADATA.forEach(function(aProp) { + addon[aProp] = aRow.getResultByName(aProp) != 0; + }); + try { + addon._sourceBundle = addon._installLocation.getLocationForID(addon.id); + } + catch (e) { + // An exception will be thrown if the add-on appears in the database but + // not on disk. In general this should only happen during startup as + // this change is being detected. + } + + this.addonCache[internal_id] = Components.utils.getWeakReference(addon); + addon._pendingCallbacks = [aCallback]; + this.fetchAddonMetadata(addon); + }, + + /** + * Synchronously reads all install locations known about by the database. This * is often a a subset of the total install locations when not all have * installed add-ons, occasionally a superset when an install location no * longer exists. @@ -1101,74 +1303,34 @@ this.XPIDatabase = { * @return an array of names of install locations */ getInstallLocations: function XPIDB_getInstallLocations() { - if (!this.addonDB) + if (!this.connection) return []; - let locations = {}; - for each (let addon in this.addonDB) { - locations[addon.location] = 1; - } - return Object.keys(locations); - }, + let stmt = this.getStatement("getInstallLocations"); - /** - * List all addons that match the filter function - * @param aFilter - * Function that takes an addon instance and returns - * true if that addon should be included in the selected array - * @return an array of DBAddonInternals - */ - _listAddons: function XPIDB_listAddons(aFilter) { - if (!this.addonDB) - return []; - - let addonList = []; - for (let key in this.addonDB) { - let addon = this.addonDB[key]; - if (aFilter(addon)) { - addonList.push(addon); - } - } - - return addonList; - }, - - /** - * Find the first addon that matches the filter function - * @param aFilter - * Function that takes an addon instance and returns - * true if that addon should be selected - * @return The first DBAddonInternal for which the filter returns true - */ - _findAddon: function XPIDB_findAddon(aFilter) { - if (!this.addonDB) - return null; - - for (let key in this.addonDB) { - let addon = this.addonDB[key]; - if (aFilter(addon)) { - return addon; - } - } - - return null; + return [row.location for each (row in resultRows(stmt))]; }, /** * Synchronously reads all the add-ons in a particular install location. * - * @param aLocation + * @param location * The name of the install location * @return an array of DBAddonInternals */ getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) { - return this._listAddons(function inLocation(aAddon) {return (aAddon.location == aLocation);}); + if (!this.connection) + return []; + + let stmt = this.getStatement("getAddonsInLocation"); + + stmt.params.location = aLocation; + return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; }, /** * Asynchronously gets an add-on with a particular ID in a particular * install location. - * XXX IRVING sync for now * * @param aId * The ID of the add-on to retrieve @@ -1178,12 +1340,30 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { - getRepositoryAddon(this.addonDB[aLocation + ":" + aId], aCallback); + if (!this.connection) { + aCallback(null); + return; + } + + let stmt = this.getStatement("getAddonInLocation"); + + stmt.params.id = aId; + stmt.params.location = aLocation; + stmt.executeAsync(new AsyncAddonListCallback(function getAddonInLocation_executeAsync(aAddons) { + if (aAddons.length == 0) { + aCallback(null); + return; + } + // This should never happen but indicates invalid data in the database if + // it does + if (aAddons.length > 1) + ERROR("Multiple addons with ID " + aId + " found in location " + aLocation); + aCallback(aAddons[0]); + })); }, /** * Asynchronously gets the add-on with an ID that is visible. - * XXX IRVING sync * * @param aId * The ID of the add-on to retrieve @@ -1191,13 +1371,29 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) { - let addon = this._findAddon(function visibleID(aAddon) {return ((aAddon.id == aId) && aAddon.visible)}); - getRepositoryAddon(addon, aCallback); + if (!this.connection) { + aCallback(null); + return; + } + + let stmt = this.getStatement("getVisibleAddonForID"); + + stmt.params.id = aId; + stmt.executeAsync(new AsyncAddonListCallback(function getVisibleAddonForID_executeAsync(aAddons) { + if (aAddons.length == 0) { + aCallback(null); + return; + } + // This should never happen but indicates invalid data in the database if + // it does + if (aAddons.length > 1) + ERROR("Multiple visible addons with ID " + aId + " found"); + aCallback(aAddons[0]); + })); }, /** * Asynchronously gets the visible add-ons, optionally restricting by type. - * XXX IRVING sync * * @param aTypes * An array of types to include or null to include all types @@ -1205,10 +1401,32 @@ this.XPIDatabase = { * A callback to pass the array of DBAddonInternals to */ getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) { - let addons = this._listAddons(function visibleType(aAddon) { - return (aAddon.visible && (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) - }); - asyncMap(addons, getRepositoryAddon, aCallback); + if (!this.connection) { + aCallback([]); + return; + } + + let stmt = null; + if (!aTypes || aTypes.length == 0) { + stmt = this.getStatement("getVisibleAddons"); + } + else { + let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " + + "type IN ("; + for (let i = 1; i <= aTypes.length; i++) { + sql += "?" + i; + if (i < aTypes.length) + sql += ","; + } + sql += ")"; + + // Note that binding to index 0 sets the value for the ?1 parameter + stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql); + for (let i = 0; i < aTypes.length; i++) + stmt.bindByIndex(i, aTypes[i]); + } + + stmt.executeAsync(new AsyncAddonListCallback(aCallback)); }, /** @@ -1219,7 +1437,13 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddonsByType: function XPIDB_getAddonsByType(aType) { - return this._listAddons(function byType(aAddon) { return aAddon.type == aType; }); + if (!this.connection) + return []; + + let stmt = this.getStatement("getAddonsByType"); + + stmt.params.type = aType; + return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; }, /** @@ -1230,14 +1454,23 @@ this.XPIDatabase = { * @return a DBAddonInternal */ getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) { - return this._findAddon(function visibleInternalName(aAddon) { - return (aAddon.visible && (aAddon.internalName == aInternalName)); - }); + if (!this.connection) + return null; + + let stmt = this.getStatement("getVisibleAddonForInternalName"); + + let addon = null; + stmt.params.internalName = aInternalName; + + if (stepStatement(stmt)) + addon = this.makeAddonFromRow(stmt.row); + + stmt.reset(); + return addon; }, /** * Asynchronously gets all add-ons with pending operations. - * XXX IRVING sync * * @param aTypes * The types of add-ons to retrieve or null to get all types @@ -1246,22 +1479,38 @@ this.XPIDatabase = { */ getVisibleAddonsWithPendingOperations: function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) { + if (!this.connection) { + aCallback([]); + return; + } - let addons = this._listAddons(function visibleType(aAddon) { - return (aAddon.visible && - (aAddon.pendingUninstall || - // Logic here is tricky. If we're active but either - // disabled flag is set, we're pending disable; if we're not - // active and neither disabled flag is set, we're pending enable - (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && - (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) - }); - asyncMap(addons, getRepositoryAddon, aCallback); + let stmt = null; + if (!aTypes || aTypes.length == 0) { + stmt = this.getStatement("getVisibleAddonsWithPendingOperations"); + } + else { + let sql = "SELECT * FROM addon WHERE visible=1 AND " + + "(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " + + "AND type IN ("; + for (let i = 1; i <= aTypes.length; i++) { + sql += "?" + i; + if (i < aTypes.length) + sql += ","; + } + sql += ")"; + + // Note that binding to index 0 sets the value for the ?1 parameter + stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" + + aTypes.length, sql); + for (let i = 0; i < aTypes.length; i++) + stmt.bindByIndex(i, aTypes[i]); + } + + stmt.executeAsync(new AsyncAddonListCallback(aCallback)); }, /** * Asynchronously get an add-on by its Sync GUID. - * XXX IRVING sync * * @param aGUID * Sync GUID of add-on to fetch @@ -1271,8 +1520,16 @@ this.XPIDatabase = { * */ getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) { - let addon = this._findAddon(function bySyncGUID(aAddon) { return aAddon.syncGUID == aGUID; }); - getRepositoryAddon(addon, aCallback); + let stmt = this.getStatement("getAddonBySyncGUID"); + stmt.params.syncGUID = aGUID; + + stmt.executeAsync(new AsyncAddonListCallback(function getAddonBySyncGUID_executeAsync(aAddons) { + if (aAddons.length == 0) { + aCallback(null); + return; + } + aCallback(aAddons[0]); + })); }, /** @@ -1281,7 +1538,12 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddons: function XPIDB_getAddons() { - return this._listAddons(function(aAddon) {return true;}); + if (!this.connection) + return []; + + let stmt = this.getStatement("getAddons"); + + return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; }, /** @@ -1291,27 +1553,92 @@ this.XPIDatabase = { * AddonInternal to add * @param aDescriptor * The file descriptor of the add-on - * @return The DBAddonInternal that was added to the database */ addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) { // If there is no DB yet then forcibly create one - // XXX IRVING I don't think this will work as expected because the addonDB - // getter will kick in. Might not matter because of the way the new DB - // creates itself. - if (!this.addonDB) + if (!this.connection) this.openConnection(false, true); this.beginTransaction(); - let newAddon = new DBAddonInternal(aAddon); - newAddon.descriptor = aDescriptor; - this.addonDB[newAddon._key] = newAddon; - if (newAddon.visible) { - this.makeAddonVisible(newAddon); + var self = this; + function insertLocale(aLocale) { + let localestmt = self.getStatement("addAddonMetadata_locale"); + let stringstmt = self.getStatement("addAddonMetadata_strings"); + + copyProperties(aLocale, PROP_LOCALE_SINGLE, localestmt.params); + executeStatement(localestmt); + let row = XPIDatabase.connection.lastInsertRowID; + + PROP_LOCALE_MULTI.forEach(function(aProp) { + aLocale[aProp].forEach(function(aStr) { + stringstmt.params.locale = row; + stringstmt.params.type = aProp; + stringstmt.params.value = aStr; + executeStatement(stringstmt); + }); + }); + return row; } - this.commitTransaction(); - return newAddon; + // Any errors in here should rollback the transaction + try { + + if (aAddon.visible) { + let stmt = this.getStatement("clearVisibleAddons"); + stmt.params.id = aAddon.id; + executeStatement(stmt); + } + + let stmt = this.getStatement("addAddonMetadata_addon"); + + stmt.params.locale = insertLocale(aAddon.defaultLocale); + stmt.params.location = aAddon._installLocation.name; + stmt.params.descriptor = aDescriptor; + copyProperties(aAddon, PROP_METADATA, stmt.params); + copyProperties(aAddon, DB_METADATA, stmt.params); + DB_BOOL_METADATA.forEach(function(aProp) { + stmt.params[aProp] = aAddon[aProp] ? 1 : 0; + }); + executeStatement(stmt); + let internal_id = this.connection.lastInsertRowID; + + stmt = this.getStatement("addAddonMetadata_addon_locale"); + aAddon.locales.forEach(function(aLocale) { + let id = insertLocale(aLocale); + aLocale.locales.forEach(function(aName) { + stmt.params.internal_id = internal_id; + stmt.params.name = aName; + stmt.params.locale = id; + executeStatement(stmt); + }); + }); + + stmt = this.getStatement("addAddonMetadata_targetApplication"); + + aAddon.targetApplications.forEach(function(aApp) { + stmt.params.internal_id = internal_id; + stmt.params.id = aApp.id; + stmt.params.minVersion = aApp.minVersion; + stmt.params.maxVersion = aApp.maxVersion; + executeStatement(stmt); + }); + + stmt = this.getStatement("addAddonMetadata_targetPlatform"); + + aAddon.targetPlatforms.forEach(function(aPlatform) { + stmt.params.internal_id = internal_id; + stmt.params.os = aPlatform.os; + stmt.params.abi = aPlatform.abi; + executeStatement(stmt); + }); + + this.commitTransaction(); + } + catch (e) { + this.rollbackTransaction(); + throw e; + } }, /** @@ -1324,7 +1651,6 @@ this.XPIDatabase = { * The new AddonInternal to add * @param aDescriptor * The file descriptor of the add-on - * @return The DBAddonInternal that was added to the database */ updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon, aDescriptor) { @@ -1340,9 +1666,38 @@ this.XPIDatabase = { aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && !aNewAddon.appDisabled && !aNewAddon.pendingUninstall) - let newDBAddon = this.addAddonMetadata(aNewAddon, aDescriptor); + this.addAddonMetadata(aNewAddon, aDescriptor); + this.commitTransaction(); + } + catch (e) { + this.rollbackTransaction(); + throw e; + } + }, + + /** + * Synchronously updates the target application entries for an add-on. + * + * @param aAddon + * The DBAddonInternal being updated + * @param aTargets + * The array of target applications to update + */ + updateTargetApplications: function XPIDB_updateTargetApplications(aAddon, + aTargets) { + this.beginTransaction(); + + // Any errors in here should rollback the transaction + try { + let stmt = this.getStatement("updateTargetApplications"); + aTargets.forEach(function(aTarget) { + stmt.params.internal_id = aAddon._internal_id; + stmt.params.id = aTarget.id; + stmt.params.minVersion = aTarget.minVersion; + stmt.params.maxVersion = aTarget.maxVersion; + executeStatement(stmt); + }); this.commitTransaction(); - return newDBAddon; } catch (e) { this.rollbackTransaction(); @@ -1357,9 +1712,9 @@ this.XPIDatabase = { * The DBAddonInternal being removed */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { - this.beginTransaction(); - delete this.addonDB[aAddon._key]; - this.commitTransaction(); + let stmt = this.getStatement("removeAddonMetadata"); + stmt.params.internal_id = aAddon._internal_id; + executeStatement(stmt); }, /** @@ -1372,17 +1727,15 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { - this.beginTransaction(); - LOG("Make addon " + aAddon._key + " visible"); - for (let key in this.addonDB) { - let otherAddon = this.addonDB[key]; - if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { - LOG("Hide addon " + otherAddon._key); - otherAddon.visible = false; - } - } + let stmt = this.getStatement("clearVisibleAddons"); + stmt.params.id = aAddon.id; + executeStatement(stmt); + + stmt = this.getStatement("makeAddonVisible"); + stmt.params.internal_id = aAddon._internal_id; + executeStatement(stmt); + aAddon.visible = true; - this.commitTransaction(); }, /** @@ -1394,11 +1747,33 @@ this.XPIDatabase = { * A dictionary of properties to set */ setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) { - this.beginTransaction(); - for (let key in aProperties) { - aAddon[key] = aProperties[key]; + function convertBoolean(value) { + return value ? 1 : 0; } - this.commitTransaction(); + + let stmt = this.getStatement("setAddonProperties"); + stmt.params.internal_id = aAddon._internal_id; + + ["userDisabled", "appDisabled", "softDisabled", + "pendingUninstall"].forEach(function(aProp) { + if (aProp in aProperties) { + stmt.params[aProp] = convertBoolean(aProperties[aProp]); + aAddon[aProp] = aProperties[aProp]; + } + else { + stmt.params[aProp] = convertBoolean(aAddon[aProp]); + } + }); + + if ("applyBackgroundUpdates" in aProperties) { + stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates; + aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates; + } + else { + stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates; + } + + executeStatement(stmt); }, /** @@ -1408,36 +1783,29 @@ this.XPIDatabase = { * The DBAddonInternal being updated * @param aGUID * GUID string to set the value to - * @throws if another addon already has the specified GUID */ setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) { - // Need to make sure no other addon has this GUID - function excludeSyncGUID(otherAddon) { - return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID); - } - let otherAddon = this._findAddon(excludeSyncGUID); - if (otherAddon) { - throw new Error("Addon sync GUID conflict for addon " + aAddon._key + - ": " + otherAddon._key + " already has GUID " + aGUID); - } - this.beginTransaction(); - aAddon.syncGUID = aGUID; - this.commitTransaction(); + let stmt = this.getStatement("setAddonSyncGUID"); + stmt.params.internal_id = aAddon._internal_id; + stmt.params.syncGUID = aGUID; + + executeStatement(stmt); }, /** * Synchronously sets the file descriptor for an add-on. - * XXX IRVING could replace this with setAddonProperties * * @param aAddon * The DBAddonInternal being updated - * @param aDescriptor - * File path of the installed addon + * @param aProperties + * A dictionary of properties to set */ setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { - this.beginTransaction(); - aAddon.descriptor = aDescriptor; - this.commitTransaction(); + let stmt = this.getStatement("setAddonDescriptor"); + stmt.params.internal_id = aAddon._internal_id; + stmt.params.descriptor = aDescriptor; + + executeStatement(stmt); }, /** @@ -1446,29 +1814,28 @@ this.XPIDatabase = { * @param aAddon * The DBAddonInternal to update */ - updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) { - LOG("Updating active state for add-on " + aAddon.id + " to " + aActive); + updateAddonActive: function XPIDB_updateAddonActive(aAddon) { + LOG("Updating add-on state"); - this.beginTransaction(); - aAddon.active = aActive; - this.commitTransaction(); + let stmt = this.getStatement("updateAddonActive"); + stmt.params.internal_id = aAddon._internal_id; + stmt.params.active = aAddon.active ? 1 : 0; + executeStatement(stmt); }, /** * Synchronously calculates and updates all the active flags in the database. */ updateActiveAddons: function XPIDB_updateActiveAddons() { - // XXX IRVING this may get called during XPI-utils shutdown - // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean LOG("Updating add-on states"); - this.beginTransaction(); - for (let key in this.addonDB) { - let addon = this.addonDB[key]; - addon.active = (addon.visible && !addon.userDisabled && - !addon.softDisabled && !addon.appDisabled && - !addon.pendingUninstall); - } - this.commitTransaction(); + let stmt = this.getStatement("setActiveAddons"); + executeStatement(stmt); + + // Note that this does not update the active property on cached + // DBAddonInternal instances so we throw away the cache. This should only + // happen during shutdown when everything is going away anyway or during + // startup when the only references are internal. + this.addonCache = []; }, /** @@ -1479,16 +1846,26 @@ this.XPIDatabase = { let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], true); + if (!this.connection) { + try { + addonsList.remove(false); + LOG("Deleted add-ons list"); + } + catch (e) { + } + + Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS); + return; + } + let enabledAddons = []; let text = "[ExtensionDirs]\r\n"; let count = 0; let fullCount = 0; - let activeAddons = this._listAddons(function active(aAddon) { - return aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme"); - }); + let stmt = this.getStatement("getActiveAddons"); - for (let row of activeAddons) { + for (let row in resultRows(stmt)) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); @@ -1504,22 +1881,17 @@ this.XPIDatabase = { dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED); } catch (e) {} - let themes = []; if (dssEnabled) { - themes = this._listAddons(function isTheme(aAddon){ return aAddon.type == "theme"; }); + stmt = this.getStatement("getThemes"); } else { - let activeTheme = this._findAddon(function isSelected(aAddon) { - return ((aAddon.type == "theme") && (aAddon.internalName == XPIProvider.selectedSkin)); - }); - if (activeTheme) { - themes.push(activeTheme); - } + stmt = this.getStatement("getActiveTheme"); + stmt.params.internalName = XPIProvider.selectedSkin; } - if (themes.length > 0) { + if (stmt) { count = 0; - for (let row of themes) { + for (let row in resultRows(stmt)) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index e0c34ee1aa35..b7bc65ddc14f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1394,60 +1394,6 @@ function do_exception_wrap(func) { }; } -const EXTENSIONS_DB = "extensions.json"; - -/** - * Change the schema version of the JSON extensions database - */ -function changeXPIDBVersion(aNewVersion) { - let dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - let jData = loadJSON(dbfile); - jData.schemaVersion = aNewVersion; - saveJSON(jData, dbfile); -} - -/** - * Raw load of a JSON file - */ -function loadJSON(aFile) { - let data = ""; - let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - fstream.init(aFile, -1, 0, 0); - cstream.init(fstream, "UTF-8", 0, 0); - let (str = {}) { - let read = 0; - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - data += str.value; - } while (read != 0); - } - cstream.close(); - do_print("Loaded JSON file " + aFile.spec); - return(JSON.parse(data)); -} - -/** - * Raw save of a JSON blob to file - */ -function saveJSON(aData, aFile) { - do_print("Starting to save JSON file " + aFile.path); - let stream = FileUtils.openSafeFileOutputStream(aFile); - let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"]. - createInstance(AM_Ci.nsIConverterOutputStream); - converter.init(stream, "UTF-8", 0, 0x0000); - // XXX pretty print the JSON while debugging - converter.writeString(JSON.stringify(aData, null, 2)); - converter.flush(); - // nsConverterOutputStream doesn't finish() safe output streams on close() - FileUtils.closeSafeFileOutputStream(stream); - converter.close(); - do_print("Done saving JSON file " + aFile.path); -} - /** * Create a callback function that calls do_execute_soon on an actual callback and arguments */ diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js index 6ebf088d6fdd..1ccdbc842c42 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js @@ -254,11 +254,14 @@ function run_test_1() { function run_test_1_modified_db() { - // After restarting the database won't be open so we can alter - // the schema - shutdownManager(); - changeXPIDBVersion(100); - startupManager(); + // After restarting the database won't be open and so can be replaced with + // a bad file + restartManager(); + var dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + var db = Services.storage.openDatabase(dbfile); + db.schemaVersion = 100; + db.close(); // Accessing the add-ons should open and recover the database. Since // migration occurs everything should be recovered correctly diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index d3b8aceb2ccb..50dcb427759d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -531,10 +531,10 @@ function manual_update(aVersion, aCallback) { // Checks that an add-ons properties match expected values function check_addon(aAddon, aExpectedVersion, aExpectedUserDisabled, aExpectedSoftDisabled, aExpectedState) { - do_check_neq(aAddon, null); dump("Testing " + aAddon.id + " version " + aAddon.version + "\n"); dump(aAddon.userDisabled + " " + aAddon.softDisabled + "\n"); + do_check_neq(aAddon, null); do_check_eq(aAddon.version, aExpectedVersion); do_check_eq(aAddon.blocklistState, aExpectedState); do_check_eq(aAddon.userDisabled, aExpectedUserDisabled); @@ -706,7 +706,11 @@ add_test(function run_app_update_schema_test() { function update_schema_2() { shutdownManager(); - changeXPIDBVersion(100); + var dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + var db = Services.storage.openDatabase(dbfile); + db.schemaVersion = 100; + db.close(); gAppInfo.version = "2"; startupManager(true); @@ -734,7 +738,11 @@ add_test(function run_app_update_schema_test() { restartManager(); shutdownManager(); - changeXPIDBVersion(100); + var dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + var db = Services.storage.openDatabase(dbfile); + db.schemaVersion = 100; + db.close(); gAppInfo.version = "2.5"; startupManager(true); @@ -756,7 +764,11 @@ add_test(function run_app_update_schema_test() { function update_schema_4() { shutdownManager(); - changeXPIDBVersion(100); + var dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + var db = Services.storage.openDatabase(dbfile); + db.schemaVersion = 100; + db.close(); startupManager(false); AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { @@ -777,7 +789,11 @@ add_test(function run_app_update_schema_test() { function update_schema_5() { shutdownManager(); - changeXPIDBVersion(100); + var dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + var db = Services.storage.openDatabase(dbfile); + db.schemaVersion = 100; + db.close(); gAppInfo.version = "1"; startupManager(true); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index 6d201640f989..bd51971a0345 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -11,6 +11,8 @@ const ADDON_UNINSTALL = 6; const ADDON_UPGRADE = 7; const ADDON_DOWNGRADE = 8; +const EXTENSIONS_DB = "extensions.sqlite"; + // This verifies that bootstrappable add-ons can be used without restarts. Components.utils.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index 789819cc6b87..4987eb51d4c8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -5,6 +5,8 @@ // This verifies that deleting the database from the profile doesn't break // anything +const EXTENSIONS_DB = "extensions.sqlite"; + const profileDir = gProfD.clone(); profileDir.append("extensions"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index 92ae8b21dc14..ea2e18a3698b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -108,8 +108,14 @@ function run_test_1() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - changeXPIDBVersion(1); + let dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + db.schemaVersion = 1; Services.prefs.setIntPref("extensions.databaseSchema", 1); + db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); @@ -249,8 +255,14 @@ function run_test_2() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - changeXPIDBVersion(1); + let dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + db.schemaVersion = 1; Services.prefs.setIntPref("extensions.databaseSchema", 1); + db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js index 50dd782da360..5cce5de71412 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -252,7 +252,7 @@ function run_test_1() { // because there is a file there still. shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.json"); + dbfile.append("extensions.sqlite"); dbfile.remove(true); dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js index e465b47bbacb..e768e2798533 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js @@ -253,7 +253,7 @@ function run_test_1() { // because there is a file there still. shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.json"); + dbfile.append("extensions.sqlite"); dbfile.remove(true); dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js b/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js new file mode 100644 index 000000000000..693ca42cda98 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js @@ -0,0 +1,181 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This tests the data in extensions.sqlite for general sanity, making sure +// rows in one table only reference rows in another table that actually exist. + + +function check_db() { + do_print("Checking DB sanity..."); + var dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + var db = Services.storage.openDatabase(dbfile); + + do_print("Checking locale_strings references rows in locale correctly..."); + let localeStringsStmt = db.createStatement("SELECT * FROM locale_strings"); + let localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); + let i = 0; + while (localeStringsStmt.executeStep()) { + i++; + localeStmt.params.locale_id = localeStringsStmt.row.locale_id; + do_check_true(localeStmt.executeStep()); + do_check_eq(localeStmt.row.count, 1); + localeStmt.reset(); + } + localeStmt.finalize(); + localeStringsStmt.finalize(); + do_print("Done. " + i + " rows in locale_strings checked."); + + + do_print("Checking locale references rows in addon_locale and addon correctly..."); + localeStmt = db.createStatement("SELECT * FROM locale"); + let addonLocaleStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon_locale WHERE locale_id=:locale_id"); + let addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE defaultLocale=:locale_id"); + i = 0; + while (localeStmt.executeStep()) { + i++; + addonLocaleStmt.params.locale_id = localeStmt.row.id; + do_check_true(addonLocaleStmt.executeStep()); + if (addonLocaleStmt.row.count == 0) { + addonStmt.params.locale_id = localeStmt.row.id; + do_check_true(addonStmt.executeStep()); + do_check_eq(addonStmt.row.count, 1); + } else { + do_check_eq(addonLocaleStmt.row.count, 1); + } + addonLocaleStmt.reset(); + addonStmt.reset(); + } + addonLocaleStmt.finalize(); + localeStmt.finalize(); + addonStmt.finalize(); + do_print("Done. " + i + " rows in locale checked."); + + + do_print("Checking addon_locale references rows in locale correctly..."); + addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); + localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); + i = 0; + while (addonLocaleStmt.executeStep()) { + i++; + localeStmt.params.locale_id = addonLocaleStmt.row.locale_id; + do_check_true(localeStmt.executeStep()); + do_check_eq(localeStmt.row.count, 1); + localeStmt.reset(); + } + addonLocaleStmt.finalize(); + localeStmt.finalize(); + do_print("Done. " + i + " rows in addon_locale checked."); + + + do_print("Checking addon_locale references rows in addon correctly..."); + addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); + addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); + i = 0; + while (addonLocaleStmt.executeStep()) { + i++; + addonStmt.params.addon_internal_id = addonLocaleStmt.row.addon_internal_id; + do_check_true(addonStmt.executeStep()); + do_check_eq(addonStmt.row.count, 1); + addonStmt.reset(); + } + addonLocaleStmt.finalize(); + addonStmt.finalize(); + do_print("Done. " + i + " rows in addon_locale checked."); + + + do_print("Checking addon references rows in locale correctly..."); + addonStmt = db.createStatement("SELECT * FROM addon"); + localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:defaultLocale"); + i = 0; + while (addonStmt.executeStep()) { + i++; + localeStmt.params.defaultLocale = addonStmt.row.defaultLocale; + do_check_true(localeStmt.executeStep()); + do_check_eq(localeStmt.row.count, 1); + localeStmt.reset(); + } + addonStmt.finalize(); + localeStmt.finalize(); + do_print("Done. " + i + " rows in addon checked."); + + + do_print("Checking targetApplication references rows in addon correctly..."); + let targetAppStmt = db.createStatement("SELECT * FROM targetApplication"); + addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); + i = 0; + while (targetAppStmt.executeStep()) { + i++; + addonStmt.params.addon_internal_id = targetAppStmt.row.addon_internal_id; + do_check_true(addonStmt.executeStep()); + do_check_eq(addonStmt.row.count, 1); + addonStmt.reset(); + } + targetAppStmt.finalize(); + addonStmt.finalize(); + do_print("Done. " + i + " rows in targetApplication checked."); + + + do_print("Checking targetPlatform references rows in addon correctly..."); + let targetPlatformStmt = db.createStatement("SELECT * FROM targetPlatform"); + addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); + i = 0; + while (targetPlatformStmt.executeStep()) { + i++; + addonStmt.params.addon_internal_id = targetPlatformStmt.row.addon_internal_id; + do_check_true(addonStmt.executeStep()); + do_check_eq(addonStmt.row.count, 1); + addonStmt.reset(); + } + targetPlatformStmt.finalize(); + addonStmt.finalize(); + do_print("Done. " + i + " rows in targetPlatform checked."); + + + db.close(); + do_print("Done checking DB sanity."); +} + +function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); + startupManager(); + + installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_1); +} + +function run_test_1() { + shutdownManager(); + check_db(); + startupManager(); + + AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { + aAddon.uninstall(); + + shutdownManager(); + check_db(); + startupManager(); + + installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_2); + }); +} + +function run_test_2() { + installAllFiles([do_get_addon("test_db_sanity_1_2")], function() { + shutdownManager(); + check_db(); + startupManager(); + run_test_3(); + }); +} + +function run_test_3() { + AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { + aAddon.uninstall(); + + shutdownManager(); + check_db(); + + do_test_finished(); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js index 883ea08c6d1f..7c9d3dc96dbb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -254,13 +254,16 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // After shutting down the database won't be open so we can - // mess with permissions + // After shutting down the database won't be open so we can lock it shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - var savedPermissions = dbfile.permissions; - dbfile.permissions = 0; + dbfile.append("extensions.sqlite"); + let connection = Services.storage.openUnsharedDatabase(dbfile); + connection.executeSimpleSQL("PRAGMA synchronous = FULL"); + connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); + // Force the DB to become locked + connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); + connection.commitTransaction(); startupManager(false); @@ -428,7 +431,7 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - dbfile.permissions = savedPermissions; + connection.close(); // After allowing access to the original DB things should go back to as // they were previously diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 6e21df540d1f..878e088e69a7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -145,9 +145,13 @@ function run_test() { // After shutting down the database won't be open so we can lock it shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - var savedPermissions = dbfile.permissions; - dbfile.permissions = 0; + dbfile.append("extensions.sqlite"); + let connection = Services.storage.openUnsharedDatabase(dbfile); + connection.executeSimpleSQL("PRAGMA synchronous = FULL"); + connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); + // Force the DB to become locked + connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); + connection.commitTransaction(); startupManager(false); @@ -199,7 +203,7 @@ function run_test() { do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - dbfile.permissions = savedPermissions; + connection.close(); // After allowing access to the original DB things should still be // applied correctly diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index 2c31171318be..0585295d26d8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -257,9 +257,13 @@ function run_test_1() { // After shutting down the database won't be open so we can lock it shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append(EXTENSIONS_DB); - var savedPermissions = dbfile.permissions; - dbfile.permissions = 0; + dbfile.append("extensions.sqlite"); + let connection = Services.storage.openUnsharedDatabase(dbfile); + connection.executeSimpleSQL("PRAGMA synchronous = FULL"); + connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); + // Force the DB to become locked + connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); + connection.commitTransaction(); startupManager(false); @@ -425,7 +429,7 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - dbfile.permissions = savedPermissions; + connection.close(); // After allowing access to the original DB things should go back to as // they were previously diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js index 20431a9a7d00..b14e332235f0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -224,7 +224,7 @@ function run_test() { do_check_true(a4.isActive); do_check_true(a4.strictCompatibility); do_check_false(a4.foreignInstall); - // addon5 was enabled in the database but needed a compatibility update + // addon5 was enabled in the database but needed a compatibiltiy update do_check_neq(a5, null); do_check_false(a5.userDisabled); do_check_false(a5.appDisabled); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js index b2903ead7d54..c8345d8c9a3d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we migrate data from a previous version of the JSON database +// Checks that we migrate data from a previous version of the sqlite database // The test extension uses an insecure update url. Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); @@ -172,8 +172,14 @@ function perform_migration() { // Turn on disabling for all scopes Services.prefs.setIntPref("extensions.autoDisableScopes", 15); - changeXPIDBVersion(1); + let dbfile = gProfD.clone(); + dbfile.append("extensions.sqlite"); + let db = AM_Cc["@mozilla.org/storage/service;1"]. + getService(AM_Ci.mozIStorageService). + openDatabase(dbfile); + db.schemaVersion = 1; Services.prefs.setIntPref("extensions.databaseSchema", 1); + db.close(); gAppInfo.version = "2" startupManager(true); @@ -241,7 +247,7 @@ function test_results() { do_check_false(a4.hasBinaryComponents); do_check_true(a4.strictCompatibility); - // addon5 was enabled in the database but needed a compatibility update + // addon5 was enabled in the database but needed a compatibiltiy update do_check_neq(a5, null); do_check_false(a5.userDisabled); do_check_false(a5.appDisabled); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js index 91a05fa358e3..323ede3114f5 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we fail to migrate but still start up ok when there is a SQLITE database +// Checks that we fail to migrate but still start up ok when there is a database // with no useful data in it. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index 841a50224169..7e25e836c9c8 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -133,7 +133,7 @@ function run_test() { check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []); let file = gProfD.clone(); - file.append("extensions.json"); + file.append("extensions.sqlite"); do_check_false(file.exists()); file.leafName = "extensions.ini"; @@ -191,7 +191,7 @@ function run_test_1() { do_check_true(gCachePurged); let file = gProfD.clone(); - file.append("extensions.json"); + file.append("extensions.sqlite"); do_check_true(file.exists()); file.leafName = "extensions.ini"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js index c73a412c4870..34cd54039f34 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js @@ -94,7 +94,8 @@ add_test(function test_error_on_duplicate_syncguid_insert() { do_throw("Should not get here."); } catch (e) { - do_check_true(e.message.startsWith("Addon sync GUID conflict")); + do_check_eq(e.result, + Components.results.NS_ERROR_STORAGE_CONSTRAINT); restartManager(); AddonManager.getAddonByID(installIDs[1], function(addon) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 49fef590e1c0..81f0eddf08a0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -17,11 +17,7 @@ skip-if = os == "android" [test_LightweightThemeManager.js] [test_backgroundupdate.js] [test_badschema.js] -# Needs rewrite for JSON XPIDB -fail-if = true [test_blocklistchange.js] -# Needs rewrite for JSON XPIDB -fail-if = true # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_blocklist_regexp.js] @@ -142,8 +138,6 @@ fail-if = os == "android" [test_bug620837.js] [test_bug655254.js] [test_bug659772.js] -# needs to be converted from sqlite to JSON -fail-if = true [test_bug675371.js] [test_bug740612.js] [test_bug753900.js] @@ -153,11 +147,8 @@ fail-if = true [test_ChromeManifestParser.js] [test_compatoverrides.js] [test_corrupt.js] -# needs to be converted from sqlite to JSON -fail-if = true [test_corrupt_strictcompat.js] -# needs to be converted from sqlite to JSON -fail-if = true +[test_db_sanity.js] [test_dictionary.js] [test_langpack.js] [test_disable.js] @@ -202,33 +193,17 @@ skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. [test_locale.js] [test_locked.js] -# Needs sqlite->JSON conversion -fail-if = true [test_locked2.js] -# Needs sqlite->JSON conversion -fail-if = true [test_locked_strictcompat.js] -# Needs sqlite->JSON conversion -fail-if = true [test_manifest.js] [test_mapURIToAddonID.js] # Same as test_bootstrap.js skip-if = os == "android" [test_migrate1.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate2.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate3.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate4.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrate5.js] -# Needs sqlite->JSON conversion -fail-if = true [test_migrateAddonRepository.js] [test_onPropertyChanged_appDisabled.js] [test_permissions.js] From 11522940baa2382a6266ec4e8cd2eefce626e2ca Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:36:37 +0200 Subject: [PATCH 31/40] Bug 888972 - part 1 - add nsIFrameScriptLoader.getDelayedFrameScripts(); r=smaug --- content/base/public/nsIMessageManager.idl | 9 ++++++++- content/base/src/nsFrameMessageManager.cpp | 23 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/content/base/public/nsIMessageManager.idl b/content/base/public/nsIMessageManager.idl index 2b8f731f4bc1..fded8f9e8206 100644 --- a/content/base/public/nsIMessageManager.idl +++ b/content/base/public/nsIMessageManager.idl @@ -5,6 +5,7 @@ #include "nsISupports.idl" +interface nsIDOMDOMStringList; interface nsIDOMWindow; interface nsIDocShell; interface nsIContent; @@ -320,7 +321,7 @@ interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager [notxpcom] nsIContent getOwnerContent(); }; -[scriptable, builtinclass, uuid(a54acd34-4141-46f5-b71b-e2ca32879b08)] +[scriptable, builtinclass, uuid(ecebfb8c-ff51-11e2-9d65-7af553959281)] interface nsIFrameScriptLoader : nsISupports { /** @@ -336,6 +337,12 @@ interface nsIFrameScriptLoader : nsISupports * Removes aURL from the list of scripts which support delayed load. */ void removeDelayedFrameScript(in AString aURL); + + /** + * Returns a list of all delayed scripts that will be loaded once + * a (remote) frame becomes available. + */ + nsIDOMDOMStringList getDelayedFrameScripts(); }; [scriptable, builtinclass, uuid(b37821ff-df79-44d4-821c-6d6ec4dfe1e9)] diff --git a/content/base/src/nsFrameMessageManager.cpp b/content/base/src/nsFrameMessageManager.cpp index 8766e0b08687..79ea4d1aca9f 100644 --- a/content/base/src/nsFrameMessageManager.cpp +++ b/content/base/src/nsFrameMessageManager.cpp @@ -33,6 +33,7 @@ #include "mozilla/dom/StructuredCloneUtils.h" #include "JavaScriptChild.h" #include "JavaScriptParent.h" +#include "nsDOMLists.h" #include #ifdef ANDROID @@ -316,6 +317,28 @@ nsFrameMessageManager::RemoveDelayedFrameScript(const nsAString& aURL) return NS_OK; } +NS_IMETHODIMP +nsFrameMessageManager::GetDelayedFrameScripts(nsIDOMDOMStringList** aList) +{ + // Frame message managers may return an incomplete list because scripts + // that were loaded after it was connected are not added to the list. + if (!IsGlobal() && !IsWindowLevel()) { + NS_WARNING("Cannot retrieve list of pending frame scripts for frame" + "message managers as it may be incomplete"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsRefPtr scripts = new nsDOMStringList(); + + for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) { + scripts->Add(mPendingScripts[i]); + } + + scripts.forget(aList); + + return NS_OK; +} + static JSBool JSONCreator(const jschar* aBuf, uint32_t aLen, void* aData) { From 9784f43b7374733610de47b74ed07e29a059a94e Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:37:03 +0200 Subject: [PATCH 32/40] Bug 888972 - part 2 - Load pending frame scripts and discard browser after swapping; r=gavin --- browser/modules/BrowserNewTabPreloader.jsm | 40 +++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/browser/modules/BrowserNewTabPreloader.jsm b/browser/modules/BrowserNewTabPreloader.jsm index 41a57c346804..9229dd0c8cef 100644 --- a/browser/modules/BrowserNewTabPreloader.jsm +++ b/browser/modules/BrowserNewTabPreloader.jsm @@ -290,14 +290,7 @@ let HiddenBrowsers = { function HiddenBrowser(width, height) { this.resize(width, height); - - HostFrame.get().then(aFrame => { - let doc = aFrame.document; - this._browser = doc.createElementNS(XUL_NS, "browser"); - this._browser.setAttribute("type", "content"); - this._browser.setAttribute("src", NEWTAB_URL); - doc.getElementById("win").appendChild(this._browser); - }); + this._createBrowser(); } HiddenBrowser.prototype = { @@ -317,7 +310,9 @@ HiddenBrowser.prototype = { return false; } - let tabbrowser = aTab.ownerDocument.defaultView.gBrowser; + let win = aTab.ownerDocument.defaultView; + let tabbrowser = win.gBrowser; + if (!tabbrowser) { return false; } @@ -325,6 +320,14 @@ HiddenBrowser.prototype = { // Swap docShells. tabbrowser.swapNewTabWithBrowser(aTab, this._browser); + // Load all default frame scripts attached to the target window. + let mm = aTab.linkedBrowser.messageManager; + let scripts = win.messageManager.getDelayedFrameScripts(); + Array.forEach(scripts, script => mm.loadFrameScript(script, true)); + + // Remove the browser, it will be recreated by a timer. + this._removeBrowser(); + // Start a timer that will kick off preloading the next newtab page. this._timer = createTimer(this, PRELOADER_INTERVAL_MS); @@ -336,7 +339,7 @@ HiddenBrowser.prototype = { this._timer = null; // Start pre-loading the new tab page. - this._browser.loadURI(NEWTAB_URL); + this._createBrowser(); }, resize: function (width, height) { @@ -350,12 +353,25 @@ HiddenBrowser.prototype = { }, destroy: function () { + this._removeBrowser(); + this._timer = clearTimer(this._timer); + }, + + _createBrowser: function () { + HostFrame.get().then(aFrame => { + let doc = aFrame.document; + this._browser = doc.createElementNS(XUL_NS, "browser"); + this._browser.setAttribute("type", "content"); + this._browser.setAttribute("src", NEWTAB_URL); + doc.getElementById("win").appendChild(this._browser); + }); + }, + + _removeBrowser: function () { if (this._browser) { this._browser.remove(); this._browser = null; } - - this._timer = clearTimer(this._timer); } }; From 9a25ff0ea8b8fa2a9330f28d1219cfe8ff699eb0 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:41:59 +0200 Subject: [PATCH 33/40] Bug 902721 - [Session Restore] Remove _dirtyWindows and replace it with a weak set; r=smacleod --- .../sessionstore/src/SessionStore.jsm | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm index 54d6263e5183..5a00b61a18f4 100644 --- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -311,9 +311,6 @@ let SessionStoreInternal = { // states for all recently closed windows _closedWindows: [], - // not-"dirty" windows usually don't need to have their data updated - _dirtyWindows: {}, - // collection of session states yet to be restored _statesToRestore: {}, @@ -1000,7 +997,7 @@ let SessionStoreInternal = { var activeWindow = this._getMostRecentBrowserWindow(); if (activeWindow) this.activeWindowSSiCache = activeWindow.__SSi || ""; - this._dirtyWindows = []; + DirtyWindows.clear(); }, /** @@ -2544,14 +2541,14 @@ let SessionStoreInternal = { this._forEachBrowserWindow(function(aWindow) { if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore return; - if (aUpdateAll || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) { + if (aUpdateAll || DirtyWindows.has(aWindow) || aWindow == activeWindow) { this._collectWindowData(aWindow); } else { // always update the window features (whose change alone never triggers a save operation) this._updateWindowFeatures(aWindow); } }); - this._dirtyWindows = []; + DirtyWindows.clear(); } // collect the data for all windows @@ -2682,7 +2679,7 @@ let SessionStoreInternal = { this._windows[aWindow.__SSi].__lastSessionWindowID = aWindow.__SS_lastSessionWindowID; - this._dirtyWindows[aWindow.__SSi] = false; + DirtyWindows.remove(aWindow); }, /* ........ Restoring Functionality .............. */ @@ -3010,7 +3007,7 @@ let SessionStoreInternal = { // It's important to set the window state to dirty so that // we collect their data for the first time when saving state. - this._dirtyWindows[aWindow.__SSi] = true; + DirtyWindows.add(aWindow); } if (aTabs.length == 0) { @@ -3701,7 +3698,7 @@ let SessionStoreInternal = { */ saveStateDelayed: function ssi_saveStateDelayed(aWindow = null, aDelay = 2000) { if (aWindow) { - this._dirtyWindows[aWindow.__SSi] = true; + DirtyWindows.add(aWindow); } if (!this._saveTimer) { @@ -4689,6 +4686,28 @@ let DyingWindowCache = { } }; +// A weak set of dirty windows. We use it to determine which windows we need to +// recollect data for when _getCurrentState() is called. +let DirtyWindows = { + _data: new WeakMap(), + + has: function (window) { + return this._data.has(window); + }, + + add: function (window) { + return this._data.set(window, true); + }, + + remove: function (window) { + this._data.delete(window); + }, + + clear: function (window) { + this._data.clear(); + } +}; + // A map storing the number of tabs last closed per windoow. This only // stores the most recent tab-close operation, and is used to undo // batch tab-closing operations. From 7941dea3eedb7ab3c5d79c70656a585015235049 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:42:20 +0200 Subject: [PATCH 34/40] Bug 902727 - [Session Restore] Remove legacy _writeFileEncoder; r=smacleod --- browser/components/sessionstore/src/SessionStore.jsm | 9 --------- 1 file changed, 9 deletions(-) diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm index 5a00b61a18f4..83da80cc6606 100644 --- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -485,8 +485,6 @@ let SessionStoreInternal = { this._prefBranch.getBoolPref("sessionstore.resume_session_once")) this._prefBranch.setBoolPref("sessionstore.resume_session_once", false); - this._initEncoding(); - this._performUpgradeBackup(); this._sessionInitialized = true; @@ -521,13 +519,6 @@ let SessionStoreInternal = { }.bind(this)); }, - _initEncoding : function ssi_initEncoding() { - // The (UTF-8) encoder used to write to files. - XPCOMUtils.defineLazyGetter(this, "_writeFileEncoder", function () { - return new TextEncoder(); - }); - }, - _initPrefs : function() { this._prefBranch = Services.prefs.getBranch("browser."); From 1a0d4f30d2c59c6b34a9965fa356601cae751fe1 Mon Sep 17 00:00:00 2001 From: Rodrigo Silveira Date: Wed, 7 Aug 2013 10:51:43 -0700 Subject: [PATCH 35/40] Bug 831440 - Switch back to static thumbnails for the tab bar --HG-- extra : rebase_source : 6af90594890ca7ae30fecfe3234e009845e6404c --- browser/metro/base/content/WebProgress.js | 1 - browser/metro/base/content/bindings/tabs.xml | 19 +++++---- browser/metro/base/content/browser.js | 41 +++++++++++++++++++- browser/metro/theme/browser.css | 1 + 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/browser/metro/base/content/WebProgress.js b/browser/metro/base/content/WebProgress.js index ab708daa69b3..130ce6d9d652 100644 --- a/browser/metro/base/content/WebProgress.js +++ b/browser/metro/base/content/WebProgress.js @@ -134,7 +134,6 @@ const WebProgress = { browser.messageManager.removeMessageListener(aMessage.name, arguments.callee); aTab._firstPaint = true; aTab.scrolledAreaChanged(true); - aTab.updateThumbnailSource(); }); }, diff --git a/browser/metro/base/content/bindings/tabs.xml b/browser/metro/base/content/bindings/tabs.xml index f4dcec2c5799..0629ac8344f9 100644 --- a/browser/metro/base/content/bindings/tabs.xml +++ b/browser/metro/base/content/bindings/tabs.xml @@ -17,7 +17,7 @@ - + @@ -36,12 +36,19 @@ - document.getAnonymousElementByAttribute(this, "anonid", "thumbnail"); + document.getAnonymousElementByAttribute(this, "anonid", "thumbnail-canvas"); document.getAnonymousElementByAttribute(this, "anonid", "close"); document.getAnonymousElementByAttribute(this, "anonid", "title"); document.getAnonymousElementByAttribute(this, "anonid", "favicon"); this.parentNode; + + + + - - - - - - diff --git a/browser/metro/base/content/browser.js b/browser/metro/base/content/browser.js index e9eba9477b27..ac98f339ca7b 100644 --- a/browser/metro/base/content/browser.js +++ b/browser/metro/base/content/browser.js @@ -8,6 +8,8 @@ let Ci = Components.interfaces; let Cu = Components.utils; let Cr = Components.results; +Cu.import("resource://gre/modules/PageThumbs.jsm"); + const kBrowserViewZoomLevelPrecision = 10000; // allow panning after this timeout on pages with registered touch listeners @@ -16,6 +18,8 @@ const kSetInactiveStateTimeout = 100; const kDefaultMetadata = { autoSize: false, allowZoom: true, autoScale: true }; +const kTabThumbnailDelayCapture = 500; + const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; // Override sizeToContent in the main window. It breaks things (bug 565887) @@ -1454,6 +1458,7 @@ function Tab(aURI, aParams, aOwner) { this._chromeTab = null; this._metadata = null; this._eventDeferred = null; + this._updateThumbnailTimeout = null; this.owner = aOwner || null; @@ -1603,13 +1608,45 @@ Tab.prototype = { self._eventDeferred = null; } browser.addEventListener("pageshow", onPageShowEvent, true); + browser.messageManager.addMessageListener("Content:StateChange", this); + Services.obs.addObserver(this, "metro_viewstate_changed", false); if (aOwner) this._copyHistoryFrom(aOwner); this._loadUsingParams(browser, aURI, aParams); }, + receiveMessage: function(aMessage) { + switch (aMessage.name) { + case "Content:StateChange": + // update the thumbnail now... + this.updateThumbnail(); + // ...and in a little while to capture page after load. + if (aMessage.json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + clearTimeout(this._updateThumbnailTimeout); + this._updateThumbnailTimeout = setTimeout(() => { + this.updateThumbnail(); + }, kTabThumbnailDelayCapture); + } + break; + } + }, + + observe: function BrowserUI_observe(aSubject, aTopic, aData) { + switch (aTopic) { + case "metro_viewstate_changed": + if (aData !== "snapped") { + this.updateThumbnail(); + } + break; + } + }, + destroy: function destroy() { + this._browser.messageManager.removeMessageListener("Content:StateChange", this); + Services.obs.removeObserver(this, "metro_viewstate_changed", false); + clearTimeout(this._updateThumbnailTimeout); + Elements.tabList.removeTab(this._chromeTab); this._chromeTab = null; this._destroyBrowser(); @@ -1818,8 +1855,8 @@ Tab.prototype = { return this.metadata.allowZoom && !Util.isURLEmpty(this.browser.currentURI.spec); }, - updateThumbnailSource: function updateThumbnailSource() { - this._chromeTab.updateThumbnailSource(this._browser); + updateThumbnail: function updateThumbnail() { + PageThumbs.captureToCanvas(this.browser.contentWindow, this._chromeTab.thumbnailCanvas); }, updateFavicon: function updateFavicon() { diff --git a/browser/metro/theme/browser.css b/browser/metro/theme/browser.css index 521d7cbbe625..90eb2537d93f 100644 --- a/browser/metro/theme/browser.css +++ b/browser/metro/theme/browser.css @@ -127,6 +127,7 @@ documenttab[closing] > .documenttab-container { margin: @metro_spacing_normal@ @metro_spacing_snormal@; background: white none center top no-repeat; background-size: cover; + min-width: @thumbnail_width@; width: @thumbnail_width@; height: @thumbnail_height@; } From 7576aec33aeb7725441d758186223b47581acb60 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:51:27 +0200 Subject: [PATCH 36/40] Bug 880101 - part 1 - Add test that forces the gBrowser constructor a little too early; r=gavin --- browser/base/content/test/Makefile.in | 1 + .../base/content/test/browser_bug880101.js | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 browser/base/content/test/browser_bug880101.js diff --git a/browser/base/content/test/Makefile.in b/browser/base/content/test/Makefile.in index 3f782e4f9756..745e3e921c06 100644 --- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -184,6 +184,7 @@ MOCHITEST_BROWSER_FILES = \ browser_bug822367.js \ browser_bug832435.js \ browser_bug839103.js \ + browser_bug880101.js \ browser_bug882977.js \ browser_bug887515.js \ browser_canonizeURL.js \ diff --git a/browser/base/content/test/browser_bug880101.js b/browser/base/content/test/browser_bug880101.js new file mode 100644 index 000000000000..abe05b8646df --- /dev/null +++ b/browser/base/content/test/browser_bug880101.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const URL = "about:robots"; + +function test() { + let win; + + let listener = { + onLocationChange: (webProgress, request, uri, flags) => { + ok(webProgress.isTopLevel, "Received onLocationChange from top frame"); + is(uri.spec, URL, "Received onLocationChange for correct URL"); + finish(); + } + }; + + waitForExplicitFinish(); + + // Remove the listener and window when we're done. + registerCleanupFunction(() => { + win.gBrowser.removeProgressListener(listener); + win.close(); + }); + + // Wait for the newly opened window. + whenNewWindowOpened(w => win = w); + + // Open a link in a new window. + openLinkIn(URL, "window", {}); + + // On the next tick, but before the window has finished loading, access the + // window's gBrowser property to force the tabbrowser constructor early. + (function tryAddProgressListener() { + executeSoon(() => { + try { + win.gBrowser.addProgressListener(listener); + } catch (e) { + // win.gBrowser wasn't ready, yet. Try again in a tick. + tryAddProgressListener(); + } + }); + })(); +} + +function whenNewWindowOpened(cb) { + Services.obs.addObserver(function obs(win) { + Services.obs.removeObserver(obs, "domwindowopened"); + cb(win); + }, "domwindowopened", false); +} From c1ac612195ea7797edcd5b12a3e04540017feb67 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 04:56:48 +0200 Subject: [PATCH 37/40] Bug 880101 - part 2 - Wait until the initial tab's content document has been created before adding the progress listener; r=gavin --- browser/base/content/browser.js | 1 - browser/base/content/tabbrowser.xml | 41 ++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 64056c5a7bdf..ed54905eefc5 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -756,7 +756,6 @@ var gBrowserInit = { // initialize observers and listeners // and give C++ access to gBrowser - gBrowser.init(); XULBrowserWindow.init(); window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(nsIWebNavigation) diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index ab04ed159049..8305311828e7 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -2956,7 +2956,17 @@ filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL); this.mTabListeners[0] = tabListener; this.mTabFilters[0] = filter; - this.init(); + + try { + // We assume this can only fail because mCurrentBrowser's docShell + // hasn't been created, yet. This may be caused by code accessing + // gBrowser before the window has finished loading. + this._addProgressListenerForInitialTab(); + } catch (e) { + // The binding was constructed too early, wait until the initial + // tab's document is ready, then add the progress listener. + this._waitForInitialContentDocument(); + } this.style.backgroundColor = Services.prefs.getBoolPref("browser.display.use_system_colors") ? @@ -2970,17 +2980,28 @@ ]]> - + + + + + { + if (this.browsers[0].contentWindow == subject) { + Services.obs.removeObserver(obs, topic); + this._addProgressListenerForInitialTab(); } - } + }; + + // We use content-document-global-created as an approximation for + // "docShell is initialized". We can do this because in the + // mTabProgressListener we care most about the STATE_STOP notification + // that will reset mBlank. That means it's important to at least add + // the progress listener before the initial about:blank load stops + // if we can't do it before the load starts. + Services.obs.addObserver(obs, "content-document-global-created", false); ]]> From d3ab83c4fa9cdc7590ef7a6d2f8f4fafdbd31448 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 05:50:21 +0200 Subject: [PATCH 38/40] Backed out changeset 24e725b72a0d (bug 792806) --- browser/components/tabview/groupitems.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/components/tabview/groupitems.js b/browser/components/tabview/groupitems.js index df3a2670cb39..df4293ffbb08 100644 --- a/browser/components/tabview/groupitems.js +++ b/browser/components/tabview/groupitems.js @@ -2560,7 +2560,7 @@ let GroupItems = { shouldShowTabView = true; } } else { - shouldHideTab = true; + shouldHideTab = true } // remove tab item from a groupItem From ee7ef6aa4ae4e64ede6c89855eabab82c5d8422f Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Fri, 9 Aug 2013 05:50:22 +0200 Subject: [PATCH 39/40] Backed out changeset 1230bd543454 (bug 792806) --- browser/components/tabview/groupitems.js | 20 +++------ .../browser_tabview_bug624265_perwindowpb.js | 44 +++++++++++++++++-- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/browser/components/tabview/groupitems.js b/browser/components/tabview/groupitems.js index df4293ffbb08..df489429c710 100644 --- a/browser/components/tabview/groupitems.js +++ b/browser/components/tabview/groupitems.js @@ -1920,7 +1920,6 @@ let GroupItems = { minGroupHeight: 110, minGroupWidth: 125, _lastActiveList: null, - _lastGroupToUpdateTabBar: null, // ---------- // Function: toString @@ -2286,10 +2285,6 @@ let GroupItems = { }); this._lastActiveList.remove(groupItem); - - if (this._lastGroupToUpdateTabBar == groupItem) - this._lastGroupToUpdateTabBar = null; - UI.updateTabButton(); }, @@ -2423,13 +2418,8 @@ let GroupItems = { Utils.assert(this._activeGroupItem, "There must be something to show in the tab bar!"); - // Update list of visible tabs only once after switching to another group. - if (this._activeGroupItem == this._lastGroupToUpdateTabBar) - return; - let tabItems = this._activeGroupItem._children; gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab)); - this._lastGroupToUpdateTabBar = this._activeGroupItem; }, // ---------- @@ -2547,7 +2537,7 @@ let GroupItems = { if (tab._tabViewTabItem.parent && tab._tabViewTabItem.parent.id == groupItemId) return; - let shouldHideTab = false; + let shouldUpdateTabBar = false; let shouldShowTabView = false; let groupItem; @@ -2555,12 +2545,12 @@ let GroupItems = { if (tab.selected) { if (gBrowser.visibleTabs.length > 1) { gBrowser._blurTab(tab); - shouldHideTab = true; + shouldUpdateTabBar = true; } else { shouldShowTabView = true; } } else { - shouldHideTab = true + shouldUpdateTabBar = true } // remove tab item from a groupItem @@ -2583,8 +2573,8 @@ let GroupItems = { new GroupItem([ tab._tabViewTabItem ], { bounds: box, immediately: true }); } - if (shouldHideTab) - gBrowser.hideTab(tab); + if (shouldUpdateTabBar) + this._updateTabBar(); else if (shouldShowTabView) UI.showTabView(); }, diff --git a/browser/components/tabview/test/browser_tabview_bug624265_perwindowpb.js b/browser/components/tabview/test/browser_tabview_bug624265_perwindowpb.js index e1d32e59e083..f6450bb7403c 100644 --- a/browser/components/tabview/test/browser_tabview_bug624265_perwindowpb.js +++ b/browser/components/tabview/test/browser_tabview_bug624265_perwindowpb.js @@ -99,14 +99,47 @@ function test() { }, aWindow); } - function testOnWindow(aCallback) { - let win = OpenBrowserWindow({private: false}); + // [624102] check state after return from private browsing + let testPrivateBrowsing = function (aWindow) { + aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#1', {inBackground: true}); + aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#2', {inBackground: true}); + + let cw = getContentWindow(aWindow); + let box = new cw.Rect(20, 20, 250, 200); + let groupItem = new cw.GroupItem([], {bounds: box, immediately: true}); + cw.UI.setActive(groupItem); + + aWindow.gBrowser.selectedTab = aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#3', {inBackground: true}); + aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#4', {inBackground: true}); + + afterAllTabsLoaded(function () { + assertNumberOfVisibleTabs(aWindow, 2); + + enterAndLeavePrivateBrowsing(function () { + assertNumberOfVisibleTabs(aWindow, 2); + aWindow.gBrowser.selectedTab = aWindow.gBrowser.tabs[0]; + closeGroupItem(cw.GroupItems.groupItems[1], function() { + next(aWindow); + }); + }); + }, aWindow); + } + + function testOnWindow(aIsPrivate, aCallback) { + let win = OpenBrowserWindow({private: aIsPrivate}); win.addEventListener("load", function onLoad() { win.removeEventListener("load", onLoad, false); executeSoon(function() { aCallback(win) }); }, false); } + function enterAndLeavePrivateBrowsing(callback) { + testOnWindow(true, function (aWindow) { + aWindow.close(); + callback(); + }); + } + waitForExplicitFinish(); // Tests for #624265 @@ -116,7 +149,10 @@ function test() { tests.push(testDuplicateTab); tests.push(testBackForwardDuplicateTab); - testOnWindow(function(aWindow) { + // Tests for #624102 + tests.push(testPrivateBrowsing); + + testOnWindow(false, function(aWindow) { loadTabView(function() { next(aWindow); }, aWindow); @@ -127,4 +163,4 @@ function loadTabView(callback, aWindow) { showTabView(function () { hideTabView(callback, aWindow); }, aWindow); -} +} \ No newline at end of file From 2ad753238efb3704262fc5be7b0bc7dc48e953ab Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Thu, 8 Aug 2013 20:54:36 -0700 Subject: [PATCH 40/40] Bug 900289: Enable foreign key pragma on FHR database. r=rnewman --- .../HealthReportDatabaseStorage.java | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java index 69625c1e2d14..9ac4117e2dcd 100644 --- a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java +++ b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.database.Cursor; import android.database.SQLException; +import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; @@ -186,7 +187,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { protected final HealthReportSQLiteOpenHelper helper; public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper { - public static final int CURRENT_VERSION = 4; + public static final int CURRENT_VERSION = 5; public static final String LOG_TAG = "HealthReportSQL"; /** @@ -227,11 +228,16 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { public static boolean CAN_USE_ABSOLUTE_DB_PATH = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO); public HealthReportSQLiteOpenHelper(Context context, File profileDirectory, String name) { + this(context, profileDirectory, name, CURRENT_VERSION); + } + + // For testing DBs of different versions. + public HealthReportSQLiteOpenHelper(Context context, File profileDirectory, String name, int version) { super( (CAN_USE_ABSOLUTE_DB_PATH ? context : new AbsolutePathContext(context, profileDirectory)), (CAN_USE_ABSOLUTE_DB_PATH ? getAbsolutePath(profileDirectory, name) : name), null, - CURRENT_VERSION); + version); if (CAN_USE_ABSOLUTE_DB_PATH) { Logger.pii(LOG_TAG, "Opening: " + getAbsolutePath(profileDirectory, name)); @@ -347,6 +353,13 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { } } + @Override + public void onOpen(SQLiteDatabase db) { + if (!db.isReadOnly()) { + db.execSQL("PRAGMA foreign_keys=ON;"); + } + } + private void createAddonsEnvironmentsView(SQLiteDatabase db) { db.execSQL("CREATE VIEW environments_with_addons AS " + "SELECT e.id AS id, " + @@ -394,6 +407,22 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { " WHERE measurement IN (SELECT id FROM measurements WHERE name = 'org.mozilla.searches.counts')"); } + private void upgradeDatabaseFrom4to5(SQLiteDatabase db) { + // Delete NULL in addons.body, which appeared as a result of Bug 886156. Note that the + // foreign key constraint, "ON DELETE RESTRICT", may be violated, but since onOpen() is + // called after this method, foreign keys are not yet enabled and constraints can be broken. + db.delete("addons", "body IS NULL", null); + + // Purge any data inconsistent with foreign key references (which may have appeared before + // foreign keys were enabled in Bug 900289). + db.delete("fields", "measurement NOT IN (SELECT id FROM measurements)", null); + db.delete("environments", "addonsID NOT IN (SELECT id from addons)", null); + db.delete(EVENTS_INTEGER, "env NOT IN (SELECT id FROM environments)", null); + db.delete(EVENTS_TEXTUAL, "env NOT IN (SELECT id FROM environments)", null); + db.delete(EVENTS_INTEGER, "field NOT IN (SELECT id FROM fields)", null); + db.delete(EVENTS_TEXTUAL, "field NOT IN (SELECT id FROM fields)", null); + } + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion >= newVersion) { @@ -408,6 +437,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { upgradeDatabaseFrom2To3(db); case 3: upgradeDatabaseFrom3To4(db); + case 4: + upgradeDatabaseFrom4to5(db); } db.setTransactionSuccessful(); } catch (Exception e) { @@ -1031,7 +1062,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { v.put("env", env); v.put("field", field); v.put("date", day); - db.insert(table, null, v); + try { + db.insertOrThrow(table, null, v); + } catch (SQLiteConstraintException e) { + throw new IllegalStateException("Event did not reference existing an environment or field.", e); + } } } @@ -1063,7 +1098,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { final SQLiteDatabase db = this.helper.getWritableDatabase(); putValue(v, value); - db.insert(table, null, v); + try { + db.insertOrThrow(table, null, v); + } catch (SQLiteConstraintException e) { + throw new IllegalStateException("Event did not reference existing an environment or field.", e); + } } @Override @@ -1133,7 +1172,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { v.put("value", by); v.put("field", field); v.put("date", day); - db.insert(EVENTS_INTEGER, null, v); + try { + db.insertOrThrow(EVENTS_INTEGER, null, v); + } catch (SQLiteConstraintException e) { + throw new IllegalStateException("Event did not reference existing an environment or field.", e); + } } }