From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- toolkit/mozapps/downloads/DownloadLastDir.jsm | 195 + toolkit/mozapps/downloads/DownloadPaths.jsm | 89 + .../mozapps/downloads/DownloadTaskbarProgress.jsm | 399 + toolkit/mozapps/downloads/DownloadUtils.jsm | 600 ++ .../downloads/content/DownloadProgressListener.js | 117 + toolkit/mozapps/downloads/content/download.xml | 327 + toolkit/mozapps/downloads/content/downloads.css | 50 + toolkit/mozapps/downloads/content/downloads.js | 1320 +++ toolkit/mozapps/downloads/content/downloads.xul | 164 + .../downloads/content/unknownContentType.xul | 107 + toolkit/mozapps/downloads/jar.mn | 12 + toolkit/mozapps/downloads/moz.build | 24 + toolkit/mozapps/downloads/nsHelperAppDlg.js | 1147 +++ toolkit/mozapps/downloads/nsHelperAppDlg.manifest | 2 + .../mozapps/downloads/tests/chrome/.eslintrc.js | 7 + toolkit/mozapps/downloads/tests/chrome/chrome.ini | 10 + .../test_unknownContentType_delayedbutton.xul | 117 + .../test_unknownContentType_dialog_layout.xul | 108 + .../unknownContentType_dialog_layout_data.pif | 1 + ...nownContentType_dialog_layout_data.pif^headers^ | 1 + .../unknownContentType_dialog_layout_data.txt | 1 + ...nownContentType_dialog_layout_data.txt^headers^ | 2 + toolkit/mozapps/downloads/tests/moz.build | 8 + toolkit/mozapps/downloads/tests/unit/.eslintrc.js | 7 + .../mozapps/downloads/tests/unit/head_downloads.js | 5 + .../downloads/tests/unit/test_DownloadPaths.js | 131 + .../downloads/tests/unit/test_DownloadUtils.js | 237 + .../downloads/tests/unit/test_lowMinutes.js | 55 + .../tests/unit/test_syncedDownloadUtils.js | 26 + .../tests/unit/test_unspecified_arguments.js | 25 + toolkit/mozapps/downloads/tests/unit/xpcshell.ini | 10 + toolkit/mozapps/extensions/.eslintrc.js | 8 + toolkit/mozapps/extensions/AddonContentPolicy.cpp | 478 + toolkit/mozapps/extensions/AddonContentPolicy.h | 22 + toolkit/mozapps/extensions/AddonManager.jsm | 3674 ++++++++ toolkit/mozapps/extensions/AddonManagerWebAPI.cpp | 171 + toolkit/mozapps/extensions/AddonManagerWebAPI.h | 33 + toolkit/mozapps/extensions/AddonPathService.cpp | 258 + toolkit/mozapps/extensions/AddonPathService.h | 55 + .../mozapps/extensions/ChromeManifestParser.jsm | 157 + toolkit/mozapps/extensions/DeferredSave.jsm | 275 + .../mozapps/extensions/LightweightThemeManager.jsm | 909 ++ toolkit/mozapps/extensions/addonManager.js | 296 + toolkit/mozapps/extensions/amContentHandler.js | 100 + toolkit/mozapps/extensions/amIAddonManager.idl | 29 + toolkit/mozapps/extensions/amIAddonPathService.idl | 37 + .../mozapps/extensions/amIWebInstallListener.idl | 134 + toolkit/mozapps/extensions/amIWebInstaller.idl | 82 + toolkit/mozapps/extensions/amInstallTrigger.js | 240 + toolkit/mozapps/extensions/amWebAPI.js | 269 + toolkit/mozapps/extensions/amWebInstallListener.js | 348 + .../extensions/content/OpenH264-license.txt | 59 + toolkit/mozapps/extensions/content/about.js | 103 + toolkit/mozapps/extensions/content/about.xul | 57 + toolkit/mozapps/extensions/content/blocklist.css | 11 + toolkit/mozapps/extensions/content/blocklist.js | 72 + toolkit/mozapps/extensions/content/blocklist.xml | 58 + toolkit/mozapps/extensions/content/blocklist.xul | 46 + toolkit/mozapps/extensions/content/eula.js | 25 + toolkit/mozapps/extensions/content/eula.xul | 35 + toolkit/mozapps/extensions/content/extensions.css | 270 + toolkit/mozapps/extensions/content/extensions.js | 3915 ++++++++ toolkit/mozapps/extensions/content/extensions.xml | 2008 +++++ toolkit/mozapps/extensions/content/extensions.xul | 715 ++ toolkit/mozapps/extensions/content/gmpPrefs.xul | 8 + toolkit/mozapps/extensions/content/list.js | 165 + toolkit/mozapps/extensions/content/list.xul | 44 + toolkit/mozapps/extensions/content/newaddon.js | 137 + toolkit/mozapps/extensions/content/newaddon.xul | 67 + toolkit/mozapps/extensions/content/pluginPrefs.xul | 20 + toolkit/mozapps/extensions/content/setting.xml | 486 + toolkit/mozapps/extensions/content/update.js | 663 ++ toolkit/mozapps/extensions/content/update.xul | 194 + toolkit/mozapps/extensions/content/updateinfo.xsl | 41 + .../extensions/content/xpinstallConfirm.css | 8 + .../mozapps/extensions/content/xpinstallConfirm.js | 196 + .../extensions/content/xpinstallConfirm.xul | 37 + .../mozapps/extensions/content/xpinstallItem.xml | 51 + toolkit/mozapps/extensions/docs/SystemAddons.rst | 224 + toolkit/mozapps/extensions/docs/index.rst | 14 + toolkit/mozapps/extensions/extensions.manifest | 27 + .../extensions/internal/APIExtensionBootstrap.js | 39 + .../mozapps/extensions/internal/AddonConstants.jsm | 31 + .../mozapps/extensions/internal/AddonLogging.jsm | 192 + .../extensions/internal/AddonRepository.jsm | 1988 +++++ .../internal/AddonRepository_SQLiteMigrator.jsm | 522 ++ .../mozapps/extensions/internal/AddonTestUtils.jsm | 1232 +++ .../extensions/internal/AddonUpdateChecker.jsm | 934 ++ toolkit/mozapps/extensions/internal/Content.js | 38 + .../extensions/internal/E10SAddonsRollout.jsm | 982 +++ .../mozapps/extensions/internal/GMPProvider.jsm | 699 ++ .../internal/LightweightThemeImageOptimizer.jsm | 180 + .../mozapps/extensions/internal/PluginProvider.jsm | 600 ++ .../extensions/internal/ProductAddonChecker.jsm | 467 + .../internal/SpellCheckDictionaryBootstrap.js | 17 + .../extensions/internal/WebExtensionBootstrap.js | 39 + .../mozapps/extensions/internal/XPIProvider.jsm | 9305 ++++++++++++++++++++ .../extensions/internal/XPIProviderUtils.js | 2255 +++++ toolkit/mozapps/extensions/internal/moz.build | 36 + toolkit/mozapps/extensions/jar.mn | 35 + toolkit/mozapps/extensions/moz.build | 66 + toolkit/mozapps/extensions/nsBlocklistService.js | 1678 ++++ .../extensions/nsBlocklistServiceContent.js | 113 + .../extensions/test/AddonManagerTesting.jsm | 115 + toolkit/mozapps/extensions/test/Makefile.in | 20 + .../test/addons/blocklist_hard1_1/install.rdf | 18 + .../test/addons/blocklist_hard1_2/install.rdf | 18 + .../test/addons/blocklist_hard1_3/install.rdf | 18 + .../test/addons/blocklist_regexp1_1/install.rdf | 18 + .../test/addons/blocklist_regexp1_2/install.rdf | 18 + .../test/addons/blocklist_regexp1_3/install.rdf | 18 + .../test/addons/blocklist_soft1_1/install.rdf | 18 + .../test/addons/blocklist_soft1_2/install.rdf | 18 + .../test/addons/blocklist_soft1_3/install.rdf | 18 + .../test/addons/blocklist_soft2_1/install.rdf | 18 + .../test/addons/blocklist_soft2_2/install.rdf | 18 + .../test/addons/blocklist_soft2_3/install.rdf | 18 + .../test/addons/blocklist_soft3_1/install.rdf | 18 + .../test/addons/blocklist_soft3_2/install.rdf | 18 + .../test/addons/blocklist_soft3_3/install.rdf | 18 + .../test/addons/blocklist_soft4_1/install.rdf | 18 + .../test/addons/blocklist_soft4_2/install.rdf | 18 + .../test/addons/blocklist_soft4_3/install.rdf | 18 + .../test/addons/blocklist_soft5_1/install.rdf | 19 + .../test/addons/blocklist_soft5_2/install.rdf | 19 + .../test/addons/blocklist_soft5_3/install.rdf | 19 + .../test/addons/bootstrap_globals/bootstrap.js | 29 + .../test/addons/bootstrap_globals/install.rdf | 23 + .../extensions/test/addons/min1max1/install.rdf | 22 + .../extensions/test/addons/min1max2/install.rdf | 22 + .../extensions/test/addons/min1max3/install.rdf | 22 + .../extensions/test/addons/min1max3b/install.rdf | 22 + .../test/addons/override1x2-1x3/install.rdf | 23 + .../test/addons/test_AddonRepository_1/install.rdf | 33 + .../test/addons/test_AddonRepository_2/install.rdf | 23 + .../test/addons/test_AddonRepository_3/icon.png | 1 + .../test/addons/test_AddonRepository_3/install.rdf | 23 + .../test/addons/test_AddonRepository_3/preview.png | 1 + .../test/addons/test_bootstrap1_1/bootstrap.js | 1 + .../test/addons/test_bootstrap1_1/install.rdf | 28 + .../test/addons/test_bootstrap1_1/version.jsm | 3 + .../test/addons/test_bootstrap1_2/bootstrap.js | 1 + .../test/addons/test_bootstrap1_2/install.rdf | 24 + .../test/addons/test_bootstrap1_2/version.jsm | 3 + .../test/addons/test_bootstrap1_3/bootstrap.js | 1 + .../test/addons/test_bootstrap1_3/install.rdf | 24 + .../test/addons/test_bootstrap1_3/version.jsm | 3 + .../test/addons/test_bootstrap1_4/install.rdf | 23 + .../test/addons/test_bootstrap2_1/bootstrap.js | 1 + .../test/addons/test_bootstrap2_1/install.rdf | 28 + .../test/addons/test_bootstrap_const/bootstrap.js | 5 + .../test/addons/test_bootstrap_const/install.rdf | 24 + .../test/addons/test_bug299716_2/install.rdf | 30 + .../test/addons/test_bug299716_a_1/install.rdf | 21 + .../test/addons/test_bug299716_a_2/install.rdf | 21 + .../test/addons/test_bug299716_b_1/install.rdf | 20 + .../test/addons/test_bug299716_b_2/install.rdf | 20 + .../test/addons/test_bug299716_c_1/install.rdf | 30 + .../test/addons/test_bug299716_c_2/install.rdf | 30 + .../test/addons/test_bug299716_d_1/install.rdf | 30 + .../test/addons/test_bug299716_d_2/install.rdf | 30 + .../test/addons/test_bug299716_e_1/install.rdf | 30 + .../test/addons/test_bug299716_e_2/install.rdf | 30 + .../test/addons/test_bug299716_f_1/install.rdf | 30 + .../test/addons/test_bug299716_f_2/install.rdf | 30 + .../test/addons/test_bug299716_g_1/install.rdf | 21 + .../test/addons/test_bug299716_g_2/install.rdf | 21 + .../test/addons/test_bug324121_1/install.rdf | 25 + .../test/addons/test_bug324121_2/install.rdf | 25 + .../test/addons/test_bug324121_3/install.rdf | 25 + .../test/addons/test_bug324121_4/install.rdf | 25 + .../test/addons/test_bug324121_5/install.rdf | 25 + .../test/addons/test_bug324121_6/install.rdf | 25 + .../test/addons/test_bug324121_7/install.rdf | 25 + .../test/addons/test_bug324121_8/install.rdf | 25 + .../test/addons/test_bug324121_9/install.rdf | 25 + .../test/addons/test_bug335238_1/install.rdf | 22 + .../test/addons/test_bug335238_2/install.rdf | 30 + .../test/addons/test_bug335238_3/install.rdf | 30 + .../test/addons/test_bug335238_4/install.rdf | 30 + .../test/addons/test_bug371495/install.rdf | 26 + .../test/addons/test_bug394300_1/install.rdf | 22 + .../test/addons/test_bug394300_2/install.rdf | 22 + .../test/addons/test_bug397778/install.rdf | 78 + .../test/addons/test_bug425657/install.rdf | 17 + .../test/addons/test_bug470377_1/install.rdf | 17 + .../test/addons/test_bug470377_2/install.rdf | 17 + .../test/addons/test_bug470377_3/install.rdf | 17 + .../test/addons/test_bug470377_4/install.rdf | 17 + .../test/addons/test_bug470377_5/install.rdf | 17 + .../test/addons/test_bug521905/install.rdf | 22 + .../test/addons/test_bug567173/install.rdf | 22 + .../test/addons/test_bug567184/bootstrap.js | 7 + .../test/addons/test_bug567184/install.rdf | 24 + .../test/addons/test_bug587088_1/install.rdf | 22 + .../test/addons/test_bug587088_1/testfile | 1 + .../test/addons/test_bug587088_1/testfile1 | 0 .../test/addons/test_bug587088_2/install.rdf | 22 + .../test/addons/test_bug587088_2/testfile | 1 + .../test/addons/test_bug587088_2/testfile2 | 0 .../test/addons/test_bug594058/directory/file1 | 0 .../test/addons/test_bug594058/install.rdf | 21 + .../test/addons/test_bug595573/install.rdf | 24 + .../test/addons/test_bug655254/install.rdf | 18 + .../test/addons/test_bug655254_2/bootstrap.js | 9 + .../test/addons/test_bug655254_2/install.rdf | 19 + .../test/addons/test_bug659772/install.rdf | 24 + .../test/addons/test_bug675371/chrome.manifest | 1 + .../test/addons/test_bug675371/install.rdf | 24 + .../extensions/test/addons/test_bug675371/test.js | 1 + .../test/addons/test_bug740612_1/bootstrap.js | 1 + .../test/addons/test_bug740612_1/install.rdf | 24 + .../test/addons/test_bug740612_2/bootstrap.js | 23 + .../test/addons/test_bug740612_2/install.rdf | 24 + .../test/addons/test_bug757663/install.rdf | 24 + .../test/addons/test_cacheflush1/install.rdf | 22 + .../test/addons/test_cacheflush2/install.rdf | 23 + .../addons/test_chromemanifest_1/chrome.manifest | 6 + .../test/addons/test_chromemanifest_1/install.rdf | 23 + .../addons/test_chromemanifest_2/chrome.manifest | 7 + .../test/addons/test_chromemanifest_2/install.rdf | 24 + .../addons/test_chromemanifest_3/chrome.manifest | 9 + .../test/addons/test_chromemanifest_3/inner.jar | Bin 0 -> 180 bytes .../test/addons/test_chromemanifest_3/install.rdf | 24 + .../addons/test_chromemanifest_4/chrome.manifest | 6 + .../components/components.manifest | 2 + .../components/other/something.manifest | 1 + .../test/addons/test_chromemanifest_4/install.rdf | 24 + .../addons/test_chromemanifest_5/chrome.manifest | 7 + .../test/addons/test_chromemanifest_5/install.rdf | 24 + .../addons/test_chromemanifest_6/chrome.manifest | 1 + .../test/addons/test_chromemanifest_6/install.rdf | 24 + .../test/addons/test_data_directory/install.rdf | 22 + .../test/addons/test_db_sanity_1_1/install.rdf | 58 + .../test/addons/test_db_sanity_1_2/install.rdf | 59 + .../test_delay_update_complete_v2/bootstrap.js | 10 + .../test_delay_update_complete_v2/install.rdf | 27 + .../manifest.json | 10 + .../addons/test_delay_update_defer_v2/bootstrap.js | 10 + .../addons/test_delay_update_defer_v2/install.rdf | 27 + .../manifest.json | 10 + .../test_delay_update_ignore_v2/bootstrap.js | 8 + .../addons/test_delay_update_ignore_v2/install.rdf | 28 + .../manifest.json | 10 + .../test/addons/test_dictionary/chrome.manifest | 1 + .../addons/test_dictionary/dictionaries/ab-CD.dic | 2 + .../test/addons/test_dictionary/install.rdf | 25 + .../test_dictionary_2/dictionaries/ab-CD.dic | 2 + .../test/addons/test_dictionary_2/install.rdf | 24 + .../test/addons/test_dictionary_3/install.rdf | 25 + .../test/addons/test_dictionary_4/install.rdf | 24 + .../test/addons/test_dictionary_5/install.rdf | 25 + .../test/addons/test_distribution1_2/install.rdf | 23 + .../test/addons/test_experiment1/bootstrap.js | 1 + .../test/addons/test_experiment1/install.rdf | 16 + .../test/addons/test_filepointer/install.rdf | 22 + .../test/addons/test_getresource/icon.png | 1 + .../test/addons/test_getresource/install.rdf | 23 + .../addons/test_getresource/subdir/subfile.txt | 1 + .../test/addons/test_hotfix_1/install.rdf | 23 + .../test/addons/test_hotfix_2/install.rdf | 23 + .../extensions/test/addons/test_install1/icon.png | 1 + .../test/addons/test_install1/icon64.png | 1 + .../test/addons/test_install1/install.rdf | 24 + .../test/addons/test_install2_1/icon.png | 1 + .../test/addons/test_install2_1/install.rdf | 24 + .../test/addons/test_install2_2/install.rdf | 24 + .../test/addons/test_install3/install.rdf | 27 + .../test/addons/test_install4/addon4.xpi | Bin 0 -> 509 bytes .../test/addons/test_install4/addon5.jar | Bin 0 -> 512 bytes .../test/addons/test_install4/addon6.xpi | Bin 0 -> 512 bytes .../test/addons/test_install4/addon7.jar | Bin 0 -> 512 bytes .../test/addons/test_install4/badaddon.jar | 1 + .../test/addons/test_install4/badaddon.xpi | 1 + .../extensions/test/addons/test_install4/icon.png | 1 + .../test/addons/test_install4/install.rdf | 10 + .../test/addons/test_install5/chrome.manifest | 1 + .../test/addons/test_install5/install.rdf | 26 + .../test/addons/test_install6/install.rdf | 24 + .../test/addons/test_install7/addon1.xpi | 1 + .../test/addons/test_install7/addon2.xpi | 1 + .../test/addons/test_install7/install.rdf | 10 + .../test/addons/test_install8/install.rdf | 10 + .../test/addons/test_jetpack/bootstrap.js | 17 + .../test/addons/test_jetpack/harness-options.json | 1 + .../test/addons/test_jetpack/install.rdf | 28 + .../test/addons/test_langpack/chrome.manifest | 1 + .../test/addons/test_langpack/install.rdf | 23 + .../extensions/test/addons/test_locale/install.rdf | 61 + .../test/addons/test_locked2_5/install.rdf | 23 + .../test/addons/test_locked2_6/install.rdf | 23 + .../test/addons/test_migrate4_6/install.rdf | 23 + .../test/addons/test_migrate4_7/install.rdf | 23 + .../test/addons/test_migrate6/install.rdf | 23 + .../test/addons/test_migrate7/install.rdf | 24 + .../test/addons/test_migrate8/chrome.manifest | 6 + .../test/addons/test_migrate8/install.rdf | 24 + .../test/addons/test_migrate9/install.rdf | 26 + .../test/addons/test_symbol/bootstrap.js | 62 + .../extensions/test/addons/test_symbol/install.rdf | 28 + .../extensions/test/addons/test_theme/install.rdf | 26 + .../extensions/test/addons/test_theme/preview.png | 1 + .../test/addons/test_undoincompatible/bootstrap.js | 1 + .../test/addons/test_undoincompatible/install.rdf | 28 + .../test/addons/test_undouninstall1/bootstrap.js | 1 + .../test/addons/test_undouninstall1/install.rdf | 28 + .../extensions/test/addons/test_update/install.rdf | 23 + .../test/addons/test_update12/install.rdf | 23 + .../test/addons/test_update8/install.rdf | 23 + .../test/addons/test_update_multi1/bootstrap.js | 5 + .../test/addons/test_update_multi1/install.rdf | 16 + .../test/addons/test_update_multi2/addon.xpi | Bin 0 -> 693 bytes .../test/addons/test_update_multi2/install.rdf | 9 + .../test/addons/test_updateid1/bootstrap.js | 5 + .../test/addons/test_updateid1/install.rdf | 16 + .../test/addons/test_updateid2/bootstrap.js | 5 + .../test/addons/test_updateid2/install.rdf | 16 + .../test/addons/upgradeable1x2-3_1/install.rdf | 22 + .../test/addons/upgradeable1x2-3_2/install.rdf | 22 + .../test/addons/webextension_1/chrome.manifest | 1 + .../test/addons/webextension_1/manifest.json | 14 + .../test/addons/webextension_2/install.rdf | 30 + .../test/addons/webextension_2/manifest.json | 10 + .../webextension_3/_locales/en/messages.json | 10 + .../webextension_3/_locales/fr/messages.json | 10 + .../test/addons/webextension_3/manifest.json | 12 + .../mozapps/extensions/test/browser/.eslintrc.js | 7 + .../extensions/test/browser/addon_about.xul | 6 + .../extensions/test/browser/addon_prefs.xul | 6 + .../test/browser/addons/browser_bug557956_1.xpi | Bin 0 -> 4426 bytes .../browser/addons/browser_bug557956_1/install.rdf | 31 + .../test/browser/addons/browser_bug557956_10.xpi | Bin 0 -> 4425 bytes .../addons/browser_bug557956_10/install.rdf | 31 + .../test/browser/addons/browser_bug557956_2.xpi | Bin 0 -> 4427 bytes .../browser/addons/browser_bug557956_2/install.rdf | 31 + .../test/browser/addons/browser_bug557956_3.xpi | Bin 0 -> 4425 bytes .../browser/addons/browser_bug557956_3/install.rdf | 31 + .../test/browser/addons/browser_bug557956_4.xpi | Bin 0 -> 4432 bytes .../browser/addons/browser_bug557956_4/install.rdf | 31 + .../test/browser/addons/browser_bug557956_5.xpi | Bin 0 -> 4427 bytes .../browser/addons/browser_bug557956_5/install.rdf | 31 + .../test/browser/addons/browser_bug557956_6.xpi | Bin 0 -> 4424 bytes .../browser/addons/browser_bug557956_6/install.rdf | 31 + .../test/browser/addons/browser_bug557956_7.xpi | Bin 0 -> 4424 bytes .../browser/addons/browser_bug557956_7/install.rdf | 31 + .../test/browser/addons/browser_bug557956_8_1.xpi | Bin 0 -> 4427 bytes .../addons/browser_bug557956_8_1/install.rdf | 31 + .../test/browser/addons/browser_bug557956_9_1.xpi | Bin 0 -> 4421 bytes .../addons/browser_bug557956_9_1/install.rdf | 31 + .../test/browser/addons/browser_bug567127_1.xpi | Bin 0 -> 4425 bytes .../browser/addons/browser_bug567127_1/install.rdf | 30 + .../test/browser/addons/browser_bug567127_2.xpi | Bin 0 -> 4427 bytes .../browser/addons/browser_bug567127_2/install.rdf | 30 + .../test/browser/addons/browser_bug596336_1.xpi | Bin 0 -> 4449 bytes .../browser/addons/browser_bug596336_1/install.rdf | 31 + .../test/browser/addons/browser_bug596336_2.xpi | Bin 0 -> 4440 bytes .../browser/addons/browser_bug596336_2/install.rdf | 31 + .../test/browser/addons/browser_dragdrop1.xpi | Bin 0 -> 4424 bytes .../browser/addons/browser_dragdrop1/install.rdf | 30 + .../test/browser/addons/browser_dragdrop2.xpi | Bin 0 -> 4420 bytes .../browser/addons/browser_dragdrop2/install.rdf | 30 + .../test/browser/addons/browser_experiment1.xpi | Bin 0 -> 4328 bytes .../browser/addons/browser_experiment1/install.rdf | 16 + .../browser/addons/browser_inlinesettings1.xpi | Bin 0 -> 5811 bytes .../addons/browser_inlinesettings1/bootstrap.js | 8 + .../addons/browser_inlinesettings1/chrome.manifest | 1 + .../addons/browser_inlinesettings1/install.rdf | 27 + .../addons/browser_inlinesettings1/options.xul | 23 + .../addons/browser_inlinesettings1/settings.dtd | 1 + .../addons/browser_inlinesettings1_custom.xpi | Bin 0 -> 6155 bytes .../browser_inlinesettings1_custom/binding.xml | 19 + .../browser_inlinesettings1_custom/bootstrap.js | 8 + .../browser_inlinesettings1_custom/chrome.manifest | 2 + .../browser_inlinesettings1_custom/install.rdf | 27 + .../browser_inlinesettings1_custom/options.xul | 5 + .../browser_inlinesettings1_custom/string.dtd | 1 + .../addons/browser_inlinesettings1_info.xpi | Bin 0 -> 5279 bytes .../browser_inlinesettings1_info/bootstrap.js | 8 + .../browser_inlinesettings1_info/install.rdf | 28 + .../browser_inlinesettings1_info/options.xul | 19 + .../test/browser/addons/browser_install1_1.xpi | Bin 0 -> 4489 bytes .../browser/addons/browser_install1_1/install.rdf | 32 + .../test/browser/addons/browser_install1_2.xpi | Bin 0 -> 4415 bytes .../browser/addons/browser_install1_2/install.rdf | 30 + .../test/browser/addons/browser_installssl.xpi | Bin 0 -> 4430 bytes .../browser/addons/browser_installssl/install.rdf | 30 + .../test/browser/addons/browser_searching.xpi | Bin 0 -> 4808 bytes .../browser/addons/browser_searching/bootstrap.js | 9 + .../browser/addons/browser_searching/install.rdf | 25 + .../test/browser/addons/browser_update1_1.xpi | Bin 0 -> 5479 bytes .../browser/addons/browser_update1_1/bootstrap.js | 12 + .../addons/browser_update1_1/chrome.manifest | 1 + .../addons/browser_update1_1/frame-script.js | 6 + .../browser/addons/browser_update1_1/install.rdf | 31 + .../test/browser/addons/browser_update1_2.xpi | Bin 0 -> 5481 bytes .../browser/addons/browser_update1_2/bootstrap.js | 12 + .../addons/browser_update1_2/chrome.manifest | 1 + .../addons/browser_update1_2/frame-script.js | 6 + .../browser/addons/browser_update1_2/install.rdf | 31 + .../test/browser/addons/browser_webapi_install.xpi | Bin 0 -> 4782 bytes .../addons/browser_webapi_install/bootstrap.js | 9 + .../addons/browser_webapi_install/install.rdf | 29 + .../test/browser/addons/options_signed.xpi | Bin 0 -> 4560 bytes .../browser/addons/options_signed/manifest.json | 11 + .../browser/addons/options_signed/options.html | 9 + .../extensions/test/browser/blockNoPlugins.xml | 7 + .../extensions/test/browser/blockPluginHard.xml | 11 + .../extensions/test/browser/browser-common.ini | 67 + .../extensions/test/browser/browser-window.ini | 52 + .../mozapps/extensions/test/browser/browser.ini | 75 + .../extensions/test/browser/browser_CTP_plugins.js | 172 + .../extensions/test/browser/browser_about.js | 84 + .../browser/browser_addonrepository_performance.js | 99 + .../extensions/test/browser/browser_bug523784.js | 120 + .../extensions/test/browser/browser_bug557943.js | 80 + .../extensions/test/browser/browser_bug557956.js | 524 ++ .../extensions/test/browser/browser_bug557956.rdf | 310 + .../extensions/test/browser/browser_bug557956.xml | 20 + .../test/browser/browser_bug557956_8_2.xpi | Bin 0 -> 4438 bytes .../test/browser/browser_bug557956_9_2.xpi | Bin 0 -> 4426 bytes .../extensions/test/browser/browser_bug562797.js | 975 ++ .../extensions/test/browser/browser_bug562854.js | 129 + .../extensions/test/browser/browser_bug562890.js | 78 + .../extensions/test/browser/browser_bug562899.js | 88 + .../extensions/test/browser/browser_bug562992.js | 70 + .../extensions/test/browser/browser_bug567127.js | 136 + .../extensions/test/browser/browser_bug567137.js | 40 + .../extensions/test/browser/browser_bug570760.js | 44 + .../extensions/test/browser/browser_bug572561.js | 99 + .../extensions/test/browser/browser_bug573062.js | 116 + .../extensions/test/browser/browser_bug577990.js | 132 + .../extensions/test/browser/browser_bug580298.js | 98 + .../extensions/test/browser/browser_bug581076.js | 132 + .../extensions/test/browser/browser_bug586574.js | 286 + .../extensions/test/browser/browser_bug587970.js | 180 + .../extensions/test/browser/browser_bug590347.js | 121 + .../extensions/test/browser/browser_bug591465.js | 512 ++ .../extensions/test/browser/browser_bug591465.xml | 35 + .../extensions/test/browser/browser_bug591663.js | 161 + .../extensions/test/browser/browser_bug593535.js | 119 + .../extensions/test/browser/browser_bug593535.xml | 34 + .../extensions/test/browser/browser_bug596336.js | 156 + .../extensions/test/browser/browser_bug608316.js | 65 + .../extensions/test/browser/browser_bug610764.js | 34 + .../extensions/test/browser/browser_bug616841.js | 21 + .../extensions/test/browser/browser_bug618502.js | 44 + .../extensions/test/browser/browser_bug679604.js | 29 + .../extensions/test/browser/browser_bug714593.js | 140 + .../test/browser/browser_cancelCompatCheck.js | 462 + .../browser/browser_checkAddonCompatibility.js | 34 + .../extensions/test/browser/browser_details.js | 1053 +++ .../extensions/test/browser/browser_discovery.js | 651 ++ .../test/browser/browser_discovery_install.js | 133 + .../extensions/test/browser/browser_dragdrop.js | 234 + .../extensions/test/browser/browser_eula.js | 85 + .../extensions/test/browser/browser_eula.xml | 35 + .../extensions/test/browser/browser_experiments.js | 654 ++ .../test/browser/browser_globalwarnings.js | 63 + .../extensions/test/browser/browser_gmpProvider.js | 418 + .../extensions/test/browser/browser_hotfix.js | 171 + .../test/browser/browser_inlinesettings.js | 680 ++ .../test/browser/browser_inlinesettings_browser.js | 207 + .../test/browser/browser_inlinesettings_custom.js | 92 + .../test/browser/browser_inlinesettings_info.js | 574 ++ .../extensions/test/browser/browser_install.js | 312 + .../extensions/test/browser/browser_install.rdf | 27 + .../test/browser/browser_install.rdf^headers^ | 1 + .../extensions/test/browser/browser_install.xml | 34 + .../extensions/test/browser/browser_install1_3.xpi | Bin 0 -> 4419 bytes .../extensions/test/browser/browser_installssl.js | 374 + .../extensions/test/browser/browser_list.js | 956 ++ .../test/browser/browser_manualupdates.js | 246 + .../test/browser/browser_metadataTimeout.js | 114 + .../extensions/test/browser/browser_newaddon.js | 232 + .../extensions/test/browser/browser_openDialog.js | 173 + .../browser/browser_plugin_enabled_state_locked.js | 124 + .../extensions/test/browser/browser_pluginprefs.js | 61 + .../extensions/test/browser/browser_purchase.js | 197 + .../extensions/test/browser/browser_purchase.xml | 180 + .../test/browser/browser_recentupdates.js | 125 + .../extensions/test/browser/browser_searching.js | 698 ++ .../extensions/test/browser/browser_searching.xml | 277 + .../test/browser/browser_searching_empty.xml | 3 + .../extensions/test/browser/browser_sorting.js | 372 + .../test/browser/browser_sorting_plugins.js | 95 + .../test/browser/browser_system_addons_are_e10s.js | 13 + .../extensions/test/browser/browser_tabsettings.js | 100 + .../test/browser/browser_task_next_test.js | 17 + .../extensions/test/browser/browser_types.js | 473 + .../test/browser/browser_uninstalling.js | 1098 +++ .../extensions/test/browser/browser_update.js | 53 + .../extensions/test/browser/browser_updateid.js | 84 + .../extensions/test/browser/browser_updatessl.js | 370 + .../extensions/test/browser/browser_updatessl.rdf | 25 + .../test/browser/browser_updatessl.rdf^headers^ | 1 + .../extensions/test/browser/browser_webapi.js | 106 + .../test/browser/browser_webapi_access.js | 127 + .../test/browser/browser_webapi_addon_listener.js | 174 + .../test/browser/browser_webapi_enable.js | 62 + .../test/browser/browser_webapi_install.js | 311 + .../test/browser/browser_webapi_uninstall.js | 51 + .../test/browser/browser_webext_options.js | 70 + .../extensions/test/browser/cancelCompatCheck.sjs | 43 + .../mozapps/extensions/test/browser/discovery.html | 10 + .../extensions/test/browser/discovery_frame.html | 6 + .../extensions/test/browser/discovery_install.html | 19 + toolkit/mozapps/extensions/test/browser/head.js | 1468 +++ .../extensions/test/browser/more_options.xul | 32 + toolkit/mozapps/extensions/test/browser/moz.build | 10 + .../mozapps/extensions/test/browser/options.xul | 12 + .../extensions/test/browser/plugin_test.html | 7 + .../mozapps/extensions/test/browser/redirect.sjs | 5 + .../extensions/test/browser/releaseNotes.xhtml | 15 + .../extensions/test/browser/signed_hotfix.rdf | 26 + .../extensions/test/browser/signed_hotfix.xpi | Bin 0 -> 2745 bytes .../extensions/test/browser/unsigned_hotfix.rdf | 26 + .../extensions/test/browser/unsigned_hotfix.xpi | Bin 0 -> 560 bytes .../test/browser/webapi_addon_listener.html | 30 + .../test/browser/webapi_checkavailable.html | 13 + .../test/browser/webapi_checkchromeframe.xul | 6 + .../test/browser/webapi_checkframed.html | 7 + .../test/browser/webapi_checknavigatedwindow.html | 28 + .../mozapps/extensions/test/mochitest/.eslintrc.js | 7 + .../extensions/test/mochitest/file_bug687194.xpi | Bin 0 -> 5659 bytes .../extensions/test/mochitest/file_empty.html | 2 + .../extensions/test/mochitest/mochitest.ini | 9 + .../extensions/test/mochitest/test_bug609794.html | 27 + .../extensions/test/mochitest/test_bug687194.html | 133 + .../extensions/test/mochitest/test_bug887098.html | 52 + toolkit/mozapps/extensions/test/moz.build | 19 + .../mozapps/extensions/test/xpcshell/.eslintrc.js | 7 + .../test/xpcshell/data/BootstrapMonitor.jsm | 30 + .../xpcshell/data/blocklistchange/addon_change.xml | 31 + .../data/blocklistchange/addon_update1.rdf | 144 + .../data/blocklistchange/addon_update2.rdf | 144 + .../data/blocklistchange/addon_update3.rdf | 144 + .../xpcshell/data/blocklistchange/app_update.xml | 62 + .../data/blocklistchange/blocklist_update1.xml | 3 + .../data/blocklistchange/blocklist_update2.xml | 26 + .../data/blocklistchange/manual_update.xml | 27 + .../test/xpcshell/data/bug455906_block.xml | 18 + .../test/xpcshell/data/bug455906_empty.xml | 7 + .../test/xpcshell/data/bug455906_start.xml | 30 + .../test/xpcshell/data/bug455906_warn.xml | 33 + .../extensions/test/xpcshell/data/corrupt.xpi | 1 + .../extensions/test/xpcshell/data/corruptfile.xpi | Bin 0 -> 633 bytes .../extensions/test/xpcshell/data/empty.xpi | Bin 0 -> 197 bytes .../test/xpcshell/data/from_sources/bootstrap.js | 1 + .../test/xpcshell/data/from_sources/install.rdf | 28 + .../test/xpcshell/data/pluginInfoURL_block.xml | 45 + .../test/xpcshell/data/productaddons/bad.txt | 1 + .../test/xpcshell/data/productaddons/bad.xml | 3 + .../test/xpcshell/data/productaddons/bad2.xml | 3 + .../test/xpcshell/data/productaddons/empty.xml | 5 + .../test/xpcshell/data/productaddons/good.xml | 11 + .../test/xpcshell/data/productaddons/missing.xml | 3 + .../test/xpcshell/data/productaddons/unsigned.xpi | Bin 0 -> 452 bytes .../data/signing_checks/bootstrap_1/bootstrap.js | 29 + .../data/signing_checks/bootstrap_1/install.rdf | 24 + .../data/signing_checks/bootstrap_1/test.txt | 1 + .../data/signing_checks/bootstrap_2/bootstrap.js | 29 + .../data/signing_checks/bootstrap_2/install.rdf | 24 + .../data/signing_checks/bootstrap_2/test.txt | 1 + .../xpcshell/data/signing_checks/hotfix_badid.xpi | Bin 0 -> 5151 bytes .../xpcshell/data/signing_checks/hotfix_broken.xpi | Bin 0 -> 5298 bytes .../xpcshell/data/signing_checks/hotfix_good.xpi | Bin 0 -> 5158 bytes .../xpcshell/data/signing_checks/long_63_hash.xpi | Bin 0 -> 4471 bytes .../xpcshell/data/signing_checks/long_63_plain.xpi | Bin 0 -> 4433 bytes .../xpcshell/data/signing_checks/long_64_hash.xpi | Bin 0 -> 4474 bytes .../xpcshell/data/signing_checks/long_64_plain.xpi | Bin 0 -> 4436 bytes .../xpcshell/data/signing_checks/long_65_hash.xpi | Bin 0 -> 4487 bytes .../xpcshell/data/signing_checks/multi_badid.xpi | Bin 0 -> 6443 bytes .../xpcshell/data/signing_checks/multi_broken.xpi | Bin 0 -> 6563 bytes .../xpcshell/data/signing_checks/multi_signed.xpi | Bin 0 -> 6425 bytes .../data/signing_checks/multi_unsigned.xpi | Bin 0 -> 2436 bytes .../data/signing_checks/nonbootstrap_1/install.rdf | 23 + .../data/signing_checks/nonbootstrap_1/test.txt | 1 + .../data/signing_checks/nonbootstrap_2/install.rdf | 23 + .../data/signing_checks/nonbootstrap_2/test.txt | 1 + .../signing_checks/preliminary_bootstrap_2.xpi | Bin 0 -> 5161 bytes .../data/signing_checks/signed_bootstrap_1.xpi | Bin 0 -> 5150 bytes .../data/signing_checks/signed_bootstrap_2.xpi | Bin 0 -> 5149 bytes .../signing_checks/signed_bootstrap_badid_2.xpi | Bin 0 -> 5155 bytes .../data/signing_checks/signed_nonbootstrap_2.xpi | Bin 0 -> 4627 bytes .../signing_checks/signed_nonbootstrap_badid_2.xpi | Bin 0 -> 4634 bytes .../data/signing_checks/unsigned_bootstrap_2.xpi | Bin 0 -> 1156 bytes .../signing_checks/unsigned_nonbootstrap_2.xpi | Bin 0 -> 691 bytes .../test/xpcshell/data/system_addons/bootstrap.js | 1 + .../test/xpcshell/data/system_addons/system1_1.xpi | Bin 0 -> 4692 bytes .../data/system_addons/system1_1_badcert.xpi | Bin 0 -> 4808 bytes .../test/xpcshell/data/system_addons/system1_2.xpi | Bin 0 -> 4695 bytes .../test/xpcshell/data/system_addons/system2_1.xpi | Bin 0 -> 4692 bytes .../test/xpcshell/data/system_addons/system2_2.xpi | Bin 0 -> 4695 bytes .../test/xpcshell/data/system_addons/system2_3.xpi | Bin 0 -> 4697 bytes .../test/xpcshell/data/system_addons/system3_1.xpi | Bin 0 -> 4689 bytes .../test/xpcshell/data/system_addons/system3_2.xpi | Bin 0 -> 4691 bytes .../test/xpcshell/data/system_addons/system3_3.xpi | Bin 0 -> 4693 bytes .../test/xpcshell/data/system_addons/system4_1.xpi | Bin 0 -> 4692 bytes .../test/xpcshell/data/system_addons/system5_1.xpi | Bin 0 -> 4691 bytes .../data/system_addons/system6_1_unpack.xpi | Bin 0 -> 4708 bytes .../data/system_addons/system6_2_notBootstrap.xpi | Bin 0 -> 4682 bytes .../system_addons/system6_3_notMultiprocess.xpi | Bin 0 -> 4675 bytes .../data/system_addons/system_delay_complete.xpi | Bin 0 -> 5090 bytes .../data/system_addons/system_delay_complete_2.xpi | Bin 0 -> 4706 bytes .../data/system_addons/system_delay_defer.xpi | Bin 0 -> 5095 bytes .../data/system_addons/system_delay_defer_2.xpi | Bin 0 -> 4701 bytes .../data/system_addons/system_delay_defer_also.xpi | Bin 0 -> 5117 bytes .../system_addons/system_delay_defer_also_2.xpi | Bin 0 -> 4716 bytes .../data/system_addons/system_delay_ignore.xpi | Bin 0 -> 5098 bytes .../data/system_addons/system_delay_ignore_2.xpi | Bin 0 -> 4707 bytes .../data/system_addons/system_failed_update.xpi | Bin 0 -> 735 bytes .../test/xpcshell/data/test_AddonRepository.xml | 820 ++ .../xpcshell/data/test_AddonRepository_cache.xml | 182 + .../test_AddonRepository_compatmode_ignore.xml | 23 + .../test_AddonRepository_compatmode_normal.xml | 23 + .../test_AddonRepository_compatmode_strict.xml | 23 + .../xpcshell/data/test_AddonRepository_empty.xml | 3 + .../xpcshell/data/test_AddonRepository_failed.xml | 21 + .../data/test_AddonRepository_getAddonsByIDs.xml | 187 + .../test/xpcshell/data/test_backgroundupdate.rdf | 70 + .../data/test_blocklist_metadata_filters_1.xml | 21 + .../test/xpcshell/data/test_blocklist_prefs_1.xml | 28 + .../test/xpcshell/data/test_blocklist_regexp_1.xml | 20 + .../test/xpcshell/data/test_bug299716.rdf | 181 + .../test/xpcshell/data/test_bug299716_2.rdf | 23 + .../test/xpcshell/data/test_bug324121.rdf | 91 + .../test/xpcshell/data/test_bug393285.xml | 30 + .../test/xpcshell/data/test_bug394300.rdf | 159 + .../test/xpcshell/data/test_bug424262.xml | 185 + .../test/xpcshell/data/test_bug449027_app.xml | 333 + .../test/xpcshell/data/test_bug449027_toolkit.xml | 208 + .../test/xpcshell/data/test_bug468528.xml | 15 + .../xpcshell/data/test_bug470377/install_1.rdf | 17 + .../xpcshell/data/test_bug470377/install_2.rdf | 17 + .../xpcshell/data/test_bug470377/install_3.rdf | 17 + .../xpcshell/data/test_bug470377/install_4.rdf | 17 + .../xpcshell/data/test_bug470377/install_5.rdf | 17 + .../test/xpcshell/data/test_bug470377/update_1.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_2.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_3.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_4.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_5.rdf | 26 + .../test/xpcshell/data/test_bug514327_1.xml | 17 + .../test/xpcshell/data/test_bug514327_2.xml | 10 + .../test/xpcshell/data/test_bug514327_3_empty.xml | 4 + .../xpcshell/data/test_bug514327_3_outdated_1.xml | 13 + .../xpcshell/data/test_bug514327_3_outdated_2.xml | 13 + .../test/xpcshell/data/test_bug526598_1.xpi | Bin 0 -> 458 bytes .../test/xpcshell/data/test_bug526598_2.xpi | Bin 0 -> 458 bytes .../test/xpcshell/data/test_bug541420.xpi | Bin 0 -> 577 bytes .../test/xpcshell/data/test_bug542391.rdf | 25 + .../test/xpcshell/data/test_bug554133.xml | 292 + .../test/xpcshell/data/test_bug619730.xml | 7 + .../test/xpcshell/data/test_bug655254.rdf | 26 + .../test/xpcshell/data/test_compatoverrides.xml | 228 + .../extensions/test/xpcshell/data/test_corrupt.rdf | 44 + .../data/test_delay_update_complete/bootstrap.js | 24 + .../data/test_delay_update_defer/bootstrap.js | 34 + .../data/test_delay_update_ignore/bootstrap.js | 26 + .../xpcshell/data/test_delay_updates_complete.json | 11 + .../xpcshell/data/test_delay_updates_complete.rdf | 26 + .../xpcshell/data/test_delay_updates_defer.json | 11 + .../xpcshell/data/test_delay_updates_defer.rdf | 26 + .../xpcshell/data/test_delay_updates_ignore.json | 11 + .../xpcshell/data/test_delay_updates_ignore.rdf | 26 + .../test/xpcshell/data/test_dictionary.rdf | 65 + .../data/test_distribution2_2/bootstrap.js | 21 + .../xpcshell/data/test_distribution2_2/install.rdf | 23 + .../data/test_distribution2_2/subdir/dummy.txt | 1 + .../test_distribution2_2/subdir/subdir2/dummy2.txt | 1 + .../test/xpcshell/data/test_gfxBlacklist.xml | 304 + .../test/xpcshell/data/test_gfxBlacklist2.xml | 31 + .../test/xpcshell/data/test_gfxBlacklist_AllOS.xml | 783 ++ .../xpcshell/data/test_gfxBlacklist_OSVersion.xml | 32 + .../test/xpcshell/data/test_hotfix_1.rdf | 26 + .../test/xpcshell/data/test_hotfix_2.rdf | 26 + .../test/xpcshell/data/test_hotfix_3.rdf | 26 + .../extensions/test/xpcshell/data/test_install.rdf | 63 + .../extensions/test/xpcshell/data/test_install.xml | 53 + .../extensions/test/xpcshell/data/test_migrate.rdf | 125 + .../test/xpcshell/data/test_migrate4.rdf | 46 + .../test/xpcshell/data/test_no_update.json | 7 + .../data/test_overrideblocklist/ancient.xml | 8 + .../xpcshell/data/test_overrideblocklist/new.xml | 8 + .../xpcshell/data/test_overrideblocklist/old.xml | 8 + .../test/xpcshell/data/test_pluginBlocklistCtp.xml | 26 + .../xpcshell/data/test_pluginBlocklistCtpUndo.xml | 10 + .../test/xpcshell/data/test_proxy/bootstrap.js | 1 + .../test/xpcshell/data/test_softblocked1.xml | 9 + .../test/xpcshell/data/test_sourceURI.xml | 18 + .../test/xpcshell/data/test_temporary/bootstrap.js | 1 + .../extensions/test/xpcshell/data/test_update.json | 215 + .../extensions/test/xpcshell/data/test_update.rdf | 270 + .../extensions/test/xpcshell/data/test_update.xml | 26 + .../test/xpcshell/data/test_update_multi.rdf | 26 + .../test/xpcshell/data/test_updatecheck.json | 327 + .../test/xpcshell/data/test_updatecheck.rdf | 419 + .../xpcshell/data/test_updatecompatmode_ignore.rdf | 26 + .../xpcshell/data/test_updatecompatmode_normal.rdf | 26 + .../xpcshell/data/test_updatecompatmode_strict.rdf | 26 + .../test/xpcshell/data/test_updateid.rdf | 26 + .../extensions/test/xpcshell/data/unsigned.xpi | Bin 0 -> 452 bytes .../test/xpcshell/data/webext-implicit-id.xpi | Bin 0 -> 4182 bytes .../extensions/test/xpcshell/head_addons.js | 1345 +++ .../extensions/test/xpcshell/head_unpack.js | 3 + .../test/xpcshell/test_AddonRepository.js | 625 ++ .../test/xpcshell/test_AddonRepository_cache.js | 704 ++ .../xpcshell/test_AddonRepository_compatmode.js | 90 + .../test/xpcshell/test_ChromeManifestParser.js | 108 + .../extensions/test/xpcshell/test_DeferredSave.js | 549 ++ .../test/xpcshell/test_LightweightThemeManager.js | 598 ++ .../test/xpcshell/test_ProductAddonChecker.js | 244 + .../extensions/test/xpcshell/test_XPIStates.js | 299 + .../extensions/test/xpcshell/test_XPIcancel.js | 66 + .../test/xpcshell/test_addon_path_service.js | 38 + .../test/xpcshell/test_asyncBlocklistLoad.js | 44 + .../test/xpcshell/test_backgroundupdate.js | 126 + .../extensions/test/xpcshell/test_bad_json.js | 54 + .../extensions/test/xpcshell/test_badschema.js | 404 + .../extensions/test/xpcshell/test_blocklist_gfx.js | 157 + .../xpcshell/test_blocklist_metadata_filters.js | 147 + .../test/xpcshell/test_blocklist_prefs.js | 148 + .../test/xpcshell/test_blocklist_regexp.js | 120 + .../test/xpcshell/test_blocklistchange.js | 1305 +++ .../extensions/test/xpcshell/test_bootstrap.js | 1403 +++ .../test/xpcshell/test_bootstrap_const.js | 17 + .../test/xpcshell/test_bootstrap_globals.js | 37 + .../test/xpcshell/test_bootstrap_resource.js | 56 + .../extensions/test/xpcshell/test_bug1180901.js | 35 + .../extensions/test/xpcshell/test_bug1180901_2.js | 60 + .../extensions/test/xpcshell/test_bug299716.js | 208 + .../extensions/test/xpcshell/test_bug299716_2.js | 50 + .../extensions/test/xpcshell/test_bug324121.js | 178 + .../extensions/test/xpcshell/test_bug335238.js | 173 + .../extensions/test/xpcshell/test_bug371495.js | 35 + .../extensions/test/xpcshell/test_bug384052.js | 103 + .../extensions/test/xpcshell/test_bug393285.js | 316 + .../extensions/test/xpcshell/test_bug394300.js | 56 + .../extensions/test/xpcshell/test_bug397778.js | 117 + .../extensions/test/xpcshell/test_bug406118.js | 155 + .../extensions/test/xpcshell/test_bug424262.js | 62 + .../extensions/test/xpcshell/test_bug425657.js | 27 + .../extensions/test/xpcshell/test_bug430120.js | 135 + .../extensions/test/xpcshell/test_bug449027.js | 429 + .../extensions/test/xpcshell/test_bug455906.js | 517 ++ .../extensions/test/xpcshell/test_bug465190.js | 39 + .../extensions/test/xpcshell/test_bug468528.js | 58 + .../extensions/test/xpcshell/test_bug470377_1.js | 49 + .../test/xpcshell/test_bug470377_1_strictcompat.js | 49 + .../extensions/test/xpcshell/test_bug470377_2.js | 49 + .../extensions/test/xpcshell/test_bug470377_3.js | 95 + .../test/xpcshell/test_bug470377_3_strictcompat.js | 94 + .../extensions/test/xpcshell/test_bug470377_4.js | 92 + .../extensions/test/xpcshell/test_bug514327_1.js | 59 + .../extensions/test/xpcshell/test_bug514327_2.js | 41 + .../extensions/test/xpcshell/test_bug514327_3.js | 139 + .../extensions/test/xpcshell/test_bug521905.js | 59 + .../extensions/test/xpcshell/test_bug526598.js | 54 + .../extensions/test/xpcshell/test_bug541420.js | 37 + .../extensions/test/xpcshell/test_bug542391.js | 464 + .../extensions/test/xpcshell/test_bug554133.js | 86 + .../extensions/test/xpcshell/test_bug559800.js | 71 + .../extensions/test/xpcshell/test_bug563256.js | 259 + .../extensions/test/xpcshell/test_bug564030.js | 63 + .../extensions/test/xpcshell/test_bug566626.js | 112 + .../extensions/test/xpcshell/test_bug567184.js | 53 + .../extensions/test/xpcshell/test_bug569138.js | 147 + .../extensions/test/xpcshell/test_bug570173.js | 61 + .../extensions/test/xpcshell/test_bug576735.js | 66 + .../extensions/test/xpcshell/test_bug587088.js | 174 + .../extensions/test/xpcshell/test_bug594058.js | 88 + .../extensions/test/xpcshell/test_bug595081.js | 27 + .../extensions/test/xpcshell/test_bug595573.js | 40 + .../extensions/test/xpcshell/test_bug596607.js | 147 + .../extensions/test/xpcshell/test_bug616841.js | 26 + .../extensions/test/xpcshell/test_bug619730.js | 64 + .../extensions/test/xpcshell/test_bug620837.js | 145 + .../extensions/test/xpcshell/test_bug655254.js | 164 + .../extensions/test/xpcshell/test_bug659772.js | 340 + .../extensions/test/xpcshell/test_bug675371.js | 91 + .../extensions/test/xpcshell/test_bug740612.js | 40 + .../extensions/test/xpcshell/test_bug753900.js | 86 + .../extensions/test/xpcshell/test_bug757663.js | 112 + .../extensions/test/xpcshell/test_bug953156.js | 51 + .../extensions/test/xpcshell/test_cache_certdb.js | 82 + .../extensions/test/xpcshell/test_cacheflush.js | 127 + .../test_checkCompatibility_themeOverride.js | 93 + .../test/xpcshell/test_checkcompatibility.js | 196 + .../extensions/test/xpcshell/test_childprocess.js | 21 + .../test/xpcshell/test_compatoverrides.js | 259 + .../extensions/test/xpcshell/test_corrupt.js | 406 + .../test/xpcshell/test_corrupt_strictcompat.js | 405 + .../extensions/test/xpcshell/test_corruptfile.js | 83 + .../extensions/test/xpcshell/test_dataDirectory.js | 50 + .../test/xpcshell/test_default_providers_pref.js | 13 + .../extensions/test/xpcshell/test_delay_update.js | 260 + .../xpcshell/test_delay_update_webextension.js | 344 + .../extensions/test/xpcshell/test_dependencies.js | 144 + .../extensions/test/xpcshell/test_dictionary.js | 811 ++ .../extensions/test/xpcshell/test_disable.js | 194 + .../extensions/test/xpcshell/test_distribution.js | 273 + .../mozapps/extensions/test/xpcshell/test_dss.js | 824 ++ .../test/xpcshell/test_duplicateplugins.js | 187 + .../test/xpcshell/test_e10s_restartless.js | 429 + .../mozapps/extensions/test/xpcshell/test_error.js | 90 + .../extensions/test/xpcshell/test_experiment.js | 131 + .../test/xpcshell/test_ext_management.js | 137 + .../extensions/test/xpcshell/test_filepointer.js | 403 + .../mozapps/extensions/test/xpcshell/test_fuel.js | 165 + .../extensions/test/xpcshell/test_general.js | 58 + .../extensions/test/xpcshell/test_getresource.js | 94 + .../test/xpcshell/test_gfxBlacklist_Device.js | 96 + .../test/xpcshell/test_gfxBlacklist_DriverNew.js | 92 + .../xpcshell/test_gfxBlacklist_Equal_DriverNew.js | 123 + .../xpcshell/test_gfxBlacklist_Equal_DriverOld.js | 93 + .../test/xpcshell/test_gfxBlacklist_Equal_OK.js | 93 + .../xpcshell/test_gfxBlacklist_GTE_DriverOld.js | 93 + .../test/xpcshell/test_gfxBlacklist_GTE_OK.js | 93 + .../xpcshell/test_gfxBlacklist_No_Comparison.js | 89 + .../test/xpcshell/test_gfxBlacklist_OK.js | 94 + .../test/xpcshell/test_gfxBlacklist_OS.js | 93 + .../xpcshell/test_gfxBlacklist_OSVersion_match.js | 95 + ...fxBlacklist_OSVersion_mismatch_DriverVersion.js | 95 + ...st_gfxBlacklist_OSVersion_mismatch_OSVersion.js | 96 + .../test/xpcshell/test_gfxBlacklist_Vendor.js | 93 + .../test/xpcshell/test_gfxBlacklist_Version.js | 145 + .../test/xpcshell/test_gfxBlacklist_prefs.js | 135 + .../extensions/test/xpcshell/test_gmpProvider.js | 416 + .../test/xpcshell/test_hasbinarycomponents.js | 82 + .../extensions/test/xpcshell/test_hotfix.js | 309 + .../extensions/test/xpcshell/test_hotfix_cert.js | 167 + .../extensions/test/xpcshell/test_install.js | 1843 ++++ .../test/xpcshell/test_install_from_sources.js | 80 + .../extensions/test/xpcshell/test_install_icons.js | 61 + .../test/xpcshell/test_install_strictcompat.js | 1726 ++++ .../extensions/test/xpcshell/test_isDebuggable.js | 36 + .../extensions/test/xpcshell/test_isReady.js | 49 + .../test/xpcshell/test_json_updatecheck.js | 372 + .../extensions/test/xpcshell/test_langpack.js | 339 + .../extensions/test/xpcshell/test_locale.js | 149 + .../extensions/test/xpcshell/test_locked.js | 544 ++ .../extensions/test/xpcshell/test_locked2.js | 297 + .../test/xpcshell/test_locked_strictcompat.js | 567 ++ .../extensions/test/xpcshell/test_manifest.js | 562 ++ .../test/xpcshell/test_mapURIToAddonID.js | 347 + .../test/xpcshell/test_metadata_update.js | 159 + .../extensions/test/xpcshell/test_migrate1.js | 231 + .../extensions/test/xpcshell/test_migrate2.js | 267 + .../extensions/test/xpcshell/test_migrate3.js | 229 + .../extensions/test/xpcshell/test_migrate4.js | 321 + .../extensions/test/xpcshell/test_migrate5.js | 139 + .../test/xpcshell/test_migrateAddonRepository.js | 127 + .../test/xpcshell/test_migrate_max_version.js | 103 + .../test/xpcshell/test_multiprocessCompatible.js | 120 + .../extensions/test/xpcshell/test_no_addons.js | 98 + .../test/xpcshell/test_nodisable_hidden.js | 107 + .../xpcshell/test_onPropertyChanged_appDisabled.js | 66 + .../test/xpcshell/test_overrideblocklist.js | 200 + .../extensions/test/xpcshell/test_pass_symbol.js | 43 + .../extensions/test/xpcshell/test_permissions.js | 86 + .../test/xpcshell/test_permissions_prefs.js | 74 + .../test/xpcshell/test_pluginBlocklistCtp.js | 182 + .../extensions/test/xpcshell/test_pluginInfoURL.js | 90 + .../extensions/test/xpcshell/test_pluginchange.js | 283 + .../extensions/test/xpcshell/test_plugins.js | 210 + .../test/xpcshell/test_pref_properties.js | 221 + .../test/xpcshell/test_provider_markSafe.js | 49 + .../test/xpcshell/test_provider_shutdown.js | 99 + .../test_provider_unsafe_access_shutdown.js | 64 + .../test_provider_unsafe_access_startup.js | 55 + .../extensions/test/xpcshell/test_proxies.js | 240 + .../mozapps/extensions/test/xpcshell/test_proxy.js | 106 + .../extensions/test/xpcshell/test_registry.js | 158 + .../extensions/test/xpcshell/test_reload.js | 235 + .../extensions/test/xpcshell/test_safemode.js | 115 + .../extensions/test/xpcshell/test_schema_change.js | 317 + .../mozapps/extensions/test/xpcshell/test_seen.js | 211 + .../test/xpcshell/test_seen_newprofile.js | 41 + .../extensions/test/xpcshell/test_shutdown.js | 85 + .../extensions/test/xpcshell/test_signed_inject.js | 382 + .../test/xpcshell/test_signed_install.js | 265 + .../extensions/test/xpcshell/test_signed_long.js | 49 + .../test/xpcshell/test_signed_migrate.js | 194 + .../extensions/test/xpcshell/test_signed_multi.js | 55 + .../test/xpcshell/test_signed_updatepref.js | 136 + .../extensions/test/xpcshell/test_signed_verify.js | 234 + .../extensions/test/xpcshell/test_softblocked.js | 109 + .../extensions/test/xpcshell/test_sourceURI.js | 66 + .../extensions/test/xpcshell/test_startup.js | 932 ++ .../test/xpcshell/test_strictcompatibility.js | 203 + .../extensions/test/xpcshell/test_switch_os.js | 52 + .../extensions/test/xpcshell/test_syncGUID.js | 156 + .../test/xpcshell/test_system_delay_update.js | 461 + .../extensions/test/xpcshell/test_system_reset.js | 418 + .../extensions/test/xpcshell/test_system_update.js | 788 ++ .../test/xpcshell/test_targetPlatforms.js | 146 + .../extensions/test/xpcshell/test_temporary.js | 760 ++ .../mozapps/extensions/test/xpcshell/test_theme.js | 1139 +++ .../mozapps/extensions/test/xpcshell/test_types.js | 65 + .../test/xpcshell/test_undothemeuninstall.js | 423 + .../extensions/test/xpcshell/test_undouninstall.js | 792 ++ .../extensions/test/xpcshell/test_uninstall.js | 216 + .../extensions/test/xpcshell/test_update.js | 1398 +++ .../extensions/test/xpcshell/test_updateCancel.js | 138 + .../test/xpcshell/test_update_compatmode.js | 184 + .../test/xpcshell/test_update_ignorecompat.js | 107 + .../test/xpcshell/test_update_strictcompat.js | 1126 +++ .../test/xpcshell/test_update_webextensions.js | 248 + .../extensions/test/xpcshell/test_updatecheck.js | 236 + .../extensions/test/xpcshell/test_updateid.js | 86 + .../extensions/test/xpcshell/test_upgrade.js | 206 + .../test/xpcshell/test_upgrade_strictcompat.js | 209 + .../extensions/test/xpcshell/test_webextension.js | 421 + .../test/xpcshell/test_webextension_embedded.js | 306 + .../test/xpcshell/test_webextension_icons.js | 169 + .../test/xpcshell/test_webextension_install.js | 478 + .../test/xpcshell/test_webextension_paths.js | 55 + .../extensions/test/xpcshell/xpcshell-shared.ini | 334 + .../extensions/test/xpcshell/xpcshell-unpack.ini | 12 + .../mozapps/extensions/test/xpcshell/xpcshell.ini | 50 + .../mozapps/extensions/test/xpinstall/.eslintrc.js | 7 + .../extensions/test/xpinstall/amosigned.xpi | Bin 0 -> 4420 bytes .../extensions/test/xpinstall/amosigned2.xpi | Bin 0 -> 4421 bytes .../extensions/test/xpinstall/authRedirect.sjs | 21 + .../mozapps/extensions/test/xpinstall/browser.ini | 119 + .../test/xpinstall/browser_amosigned_trigger.js | 56 + .../xpinstall/browser_amosigned_trigger_iframe.js | 57 + .../test/xpinstall/browser_amosigned_url.js | 35 + .../extensions/test/xpinstall/browser_auth.js | 47 + .../extensions/test/xpinstall/browser_auth2.js | 46 + .../extensions/test/xpinstall/browser_auth3.js | 53 + .../extensions/test/xpinstall/browser_auth4.js | 52 + .../extensions/test/xpinstall/browser_badargs.js | 38 + .../extensions/test/xpinstall/browser_badargs2.js | 42 + .../extensions/test/xpinstall/browser_badhash.js | 33 + .../test/xpinstall/browser_badhashtype.js | 33 + .../extensions/test/xpinstall/browser_bug540558.js | 25 + .../extensions/test/xpinstall/browser_bug611242.js | 17 + .../extensions/test/xpinstall/browser_bug638292.js | 40 + .../extensions/test/xpinstall/browser_bug645699.js | 36 + .../extensions/test/xpinstall/browser_bug672485.js | 52 + .../extensions/test/xpinstall/browser_cancel.js | 60 + .../test/xpinstall/browser_concurrent_installs.js | 127 + .../extensions/test/xpinstall/browser_cookies.js | 30 + .../extensions/test/xpinstall/browser_cookies2.js | 40 + .../extensions/test/xpinstall/browser_cookies3.js | 44 + .../extensions/test/xpinstall/browser_cookies4.js | 43 + .../extensions/test/xpinstall/browser_corrupt.js | 38 + .../extensions/test/xpinstall/browser_datauri.js | 37 + .../extensions/test/xpinstall/browser_empty.js | 28 + .../extensions/test/xpinstall/browser_enabled.js | 29 + .../extensions/test/xpinstall/browser_enabled2.js | 32 + .../extensions/test/xpinstall/browser_enabled3.js | 52 + .../extensions/test/xpinstall/browser_hash.js | 34 + .../extensions/test/xpinstall/browser_hash2.js | 34 + .../extensions/test/xpinstall/browser_httphash.js | 39 + .../extensions/test/xpinstall/browser_httphash2.js | 39 + .../extensions/test/xpinstall/browser_httphash3.js | 39 + .../extensions/test/xpinstall/browser_httphash4.js | 36 + .../extensions/test/xpinstall/browser_httphash5.js | 40 + .../extensions/test/xpinstall/browser_httphash6.js | 83 + .../test/xpinstall/browser_installchrome.js | 25 + .../extensions/test/xpinstall/browser_localfile.js | 35 + .../test/xpinstall/browser_localfile2.js | 38 + .../test/xpinstall/browser_localfile3.js | 41 + .../test/xpinstall/browser_localfile4.js | 41 + .../test/xpinstall/browser_multipackage.js | 52 + .../test/xpinstall/browser_navigateaway.js | 36 + .../test/xpinstall/browser_navigateaway2.js | 34 + .../test/xpinstall/browser_navigateaway3.js | 38 + .../test/xpinstall/browser_navigateaway4.js | 44 + .../extensions/test/xpinstall/browser_offline.js | 62 + .../extensions/test/xpinstall/browser_relative.js | 55 + .../test/xpinstall/browser_signed_multipackage.js | 53 + .../test/xpinstall/browser_signed_multiple.js | 72 + .../test/xpinstall/browser_signed_naming.js | 67 + .../test/xpinstall/browser_signed_tampered.js | 33 + .../test/xpinstall/browser_signed_trigger.js | 41 + .../test/xpinstall/browser_signed_untrusted.js | 41 + .../test/xpinstall/browser_signed_url.js | 34 + .../test/xpinstall/browser_softwareupdate.js | 25 + .../extensions/test/xpinstall/browser_switchtab.js | 49 + .../test/xpinstall/browser_trigger_redirect.js | 41 + .../test/xpinstall/browser_unsigned_trigger.js | 56 + .../xpinstall/browser_unsigned_trigger_iframe.js | 57 + .../xpinstall/browser_unsigned_trigger_xorigin.js | 38 + .../test/xpinstall/browser_unsigned_url.js | 35 + .../extensions/test/xpinstall/browser_whitelist.js | 53 + .../test/xpinstall/browser_whitelist2.js | 31 + .../test/xpinstall/browser_whitelist3.js | 28 + .../test/xpinstall/browser_whitelist4.js | 30 + .../test/xpinstall/browser_whitelist5.js | 25 + .../test/xpinstall/browser_whitelist6.js | 25 + .../test/xpinstall/browser_whitelist7.js | 32 + .../extensions/test/xpinstall/bug540558.html | 23 + .../extensions/test/xpinstall/bug638292.html | 17 + .../extensions/test/xpinstall/bug645699.html | 31 + .../test/xpinstall/concurrent_installs.html | 40 + .../extensions/test/xpinstall/cookieRedirect.sjs | 24 + .../mozapps/extensions/test/xpinstall/corrupt.xpi | 1 + .../mozapps/extensions/test/xpinstall/empty.xpi | Bin 0 -> 197 bytes .../mozapps/extensions/test/xpinstall/enabled.html | 24 + .../extensions/test/xpinstall/hashRedirect.sjs | 15 + toolkit/mozapps/extensions/test/xpinstall/head.js | 434 + .../extensions/test/xpinstall/incompatible.xpi | Bin 0 -> 4442 bytes .../extensions/test/xpinstall/installchrome.html | 22 + .../extensions/test/xpinstall/installtrigger.html | 44 + .../test/xpinstall/installtrigger_frame.html | 29 + .../extensions/test/xpinstall/multipackage.xpi | Bin 0 -> 9589 bytes .../extensions/test/xpinstall/navigate.html | 26 + .../mozapps/extensions/test/xpinstall/redirect.sjs | 45 + .../test/xpinstall/restartless-unsigned.xpi | Bin 0 -> 528 bytes .../extensions/test/xpinstall/restartless.xpi | Bin 0 -> 4447 bytes .../test/xpinstall/signed-multipackage.xpi | Bin 0 -> 2976 bytes .../extensions/test/xpinstall/signed-no-cn.xpi | Bin 0 -> 2241 bytes .../extensions/test/xpinstall/signed-no-o.xpi | Bin 0 -> 2247 bytes .../extensions/test/xpinstall/signed-tampered.xpi | Bin 0 -> 2260 bytes .../extensions/test/xpinstall/signed-untrusted.xpi | Bin 0 -> 2237 bytes .../mozapps/extensions/test/xpinstall/signed.xpi | Bin 0 -> 2250 bytes .../mozapps/extensions/test/xpinstall/signed2.xpi | Bin 0 -> 2938 bytes .../extensions/test/xpinstall/slowinstall.sjs | 101 + .../test/xpinstall/startsoftwareupdate.html | 20 + .../mozapps/extensions/test/xpinstall/theme.xpi | Bin 0 -> 4450 bytes .../extensions/test/xpinstall/triggerredirect.html | 36 + .../mozapps/extensions/test/xpinstall/unsigned.xpi | Bin 0 -> 452 bytes toolkit/mozapps/handling/content/dialog.js | 278 + toolkit/mozapps/handling/content/dialog.xul | 52 + toolkit/mozapps/handling/content/handler.css | 11 + toolkit/mozapps/handling/content/handler.xml | 28 + toolkit/mozapps/handling/jar.mn | 10 + toolkit/mozapps/handling/moz.build | 12 + .../mozapps/handling/nsContentDispatchChooser.js | 85 + .../handling/nsContentDispatchChooser.manifest | 2 + toolkit/mozapps/installer/find-dupes.py | 135 + toolkit/mozapps/installer/informulate.py | 46 + toolkit/mozapps/installer/js-compare-ast.js | 28 + toolkit/mozapps/installer/l10n-repack.py | 60 + .../mozapps/installer/linux/rpm/mozilla.desktop | 21 + toolkit/mozapps/installer/linux/rpm/mozilla.spec | 134 + toolkit/mozapps/installer/make-eme.mk | 16 + toolkit/mozapps/installer/package-name.mk | 168 + toolkit/mozapps/installer/packager.mk | 250 + toolkit/mozapps/installer/packager.py | 415 + toolkit/mozapps/installer/precompile_cache.js | 87 + toolkit/mozapps/installer/signing.mk | 41 + toolkit/mozapps/installer/strip.py | 23 + toolkit/mozapps/installer/unpack.py | 22 + toolkit/mozapps/installer/upload-files-APK.mk | 141 + toolkit/mozapps/installer/upload-files.mk | 529 ++ toolkit/mozapps/installer/windows/nsis/common.nsh | 8024 +++++++++++++++++ .../installer/windows/nsis/locale-fonts.nsh | 681 ++ .../mozapps/installer/windows/nsis/locale-rtl.nlf | 12 + toolkit/mozapps/installer/windows/nsis/locale.nlf | 12 + toolkit/mozapps/installer/windows/nsis/locales.nsi | 23 + toolkit/mozapps/installer/windows/nsis/makensis.mk | 100 + .../mozapps/installer/windows/nsis/overrides.nsh | 604 ++ .../installer/windows/nsis/preprocess-locale.py | 360 + toolkit/mozapps/installer/windows/nsis/setup.ico | Bin 0 -> 25214 bytes toolkit/mozapps/preferences/changemp.js | 237 + toolkit/mozapps/preferences/changemp.xul | 67 + toolkit/mozapps/preferences/fontbuilder.js | 126 + toolkit/mozapps/preferences/jar.mn | 11 + toolkit/mozapps/preferences/moz.build | 7 + toolkit/mozapps/preferences/removemp.js | 56 + toolkit/mozapps/preferences/removemp.xul | 46 + toolkit/mozapps/update/UpdateTelemetry.jsm | 488 + toolkit/mozapps/update/common-standalone/moz.build | 12 + toolkit/mozapps/update/common/certificatecheck.cpp | 250 + toolkit/mozapps/update/common/certificatecheck.h | 22 + toolkit/mozapps/update/common/errors.h | 110 + toolkit/mozapps/update/common/moz.build | 32 + toolkit/mozapps/update/common/pathhash.cpp | 139 + toolkit/mozapps/update/common/pathhash.h | 19 + toolkit/mozapps/update/common/readstrings.cpp | 236 + toolkit/mozapps/update/common/readstrings.h | 43 + .../mozapps/update/common/registrycertificates.cpp | 154 + .../mozapps/update/common/registrycertificates.h | 14 + toolkit/mozapps/update/common/sources.mozbuild | 28 + toolkit/mozapps/update/common/uachelper.cpp | 222 + toolkit/mozapps/update/common/uachelper.h | 23 + toolkit/mozapps/update/common/updatecommon.cpp | 213 + toolkit/mozapps/update/common/updatecommon.h | 47 + toolkit/mozapps/update/common/updatedefines.h | 142 + toolkit/mozapps/update/common/updatehelper.cpp | 609 ++ toolkit/mozapps/update/common/updatehelper.h | 29 + toolkit/mozapps/update/common/win_dirent.h | 32 + toolkit/mozapps/update/content/history.js | 70 + toolkit/mozapps/update/content/history.xul | 39 + toolkit/mozapps/update/content/updates.css | 33 + toolkit/mozapps/update/content/updates.js | 1399 +++ toolkit/mozapps/update/content/updates.xml | 83 + toolkit/mozapps/update/content/updates.xul | 206 + toolkit/mozapps/update/jar.mn | 12 + toolkit/mozapps/update/moz.build | 33 + toolkit/mozapps/update/nsIUpdateService.idl | 575 ++ toolkit/mozapps/update/nsUpdateService.js | 4567 ++++++++++ toolkit/mozapps/update/nsUpdateService.manifest | 12 + toolkit/mozapps/update/nsUpdateServiceStub.js | 45 + toolkit/mozapps/update/tests/Makefile.in | 39 + toolkit/mozapps/update/tests/TestAUSHelper.cpp | 423 + .../mozapps/update/tests/TestAUSReadStrings.cpp | 172 + .../mozapps/update/tests/TestAUSReadStrings1.ini | 47 + .../mozapps/update/tests/TestAUSReadStrings2.ini | 39 + .../mozapps/update/tests/TestAUSReadStrings3.ini | 39 + toolkit/mozapps/update/tests/chrome/.eslintrc.js | 7 + toolkit/mozapps/update/tests/chrome/chrome.ini | 64 + .../tests/chrome/test_0010_background_basic.xul | 50 + .../update/tests/chrome/test_0011_check_basic.xul | 51 + .../tests/chrome/test_0012_check_basic_staging.xul | 55 + .../tests/chrome/test_0013_check_no_updates.xul | 46 + .../chrome/test_0014_check_error_xml_malformed.xul | 46 + ...est_0061_check_verifyFailPartial_noComplete.xul | 52 + ...est_0062_check_verifyFailComplete_noPartial.xul | 52 + .../test_0063_check_verifyFailPartialComplete.xul | 52 + ...064_check_verifyFailPartial_successComplete.xul | 52 + ...st_0071_notify_verifyFailPartial_noComplete.xul | 53 + ...st_0072_notify_verifyFailComplete_noPartial.xul | 52 + .../test_0073_notify_verifyFailPartialComplete.xul | 55 + ...74_notify_verifyFailPartial_successComplete.xul | 55 + ...t_0081_error_patchApplyFailure_partial_only.xul | 53 + ..._0082_error_patchApplyFailure_complete_only.xul | 52 + ...83_error_patchApplyFailure_partial_complete.xul | 67 + ..._0084_error_patchApplyFailure_verify_failed.xul | 68 + ..._patchApplyFailure_partial_complete_staging.xul | 94 + .../tests/chrome/test_0092_finishedBackground.xul | 55 + .../tests/chrome/test_0093_restartNotification.xul | 60 + .../test_0094_restartNotification_remote.xul | 60 + ...095_restartNotification_remoteInvalidNumber.xul | 66 + ...t_0096_restartNotification_stagedBackground.xul | 65 + ...restartNotification_stagedServiceBackground.xul | 65 + .../test_0101_background_restartNotification.xul | 46 + ...0102_background_restartNotification_staging.xul | 49 + ...ckground_restartNotification_stagingService.xul | 50 + .../tests/chrome/test_0111_neverButton_basic.xul | 61 + ...est_0113_showNeverForVersionRemovedWithPref.xul | 58 + .../test_0151_notify_backgroundCheckError.xul | 50 + ...est_0152_notify_backgroundCheckOfflineRetry.xul | 96 + .../tests/chrome/test_0161_check_unsupported.xul | 50 + .../tests/chrome/test_0162_notify_unsupported.xul | 44 + .../chrome/test_0171_check_noPerms_manual.xul | 64 + .../chrome/test_0172_notify_noPerms_manual.xul | 63 + .../update/tests/chrome/test_9999_cleanup.xul | 112 + toolkit/mozapps/update/tests/chrome/update.sjs | 194 + toolkit/mozapps/update/tests/chrome/utils.js | 1011 +++ toolkit/mozapps/update/tests/data/complete.exe | Bin 0 -> 79872 bytes toolkit/mozapps/update/tests/data/complete.mar | Bin 0 -> 97888 bytes toolkit/mozapps/update/tests/data/complete.png | Bin 0 -> 878 bytes .../update/tests/data/complete_log_success_mac | 332 + .../update/tests/data/complete_log_success_win | 320 + toolkit/mozapps/update/tests/data/complete_mac.mar | Bin 0 -> 98454 bytes .../mozapps/update/tests/data/complete_precomplete | 18 + .../update/tests/data/complete_precomplete_mac | 21 + .../update/tests/data/complete_removed-files | 41 + .../update/tests/data/complete_removed-files_mac | 41 + .../update/tests/data/complete_update_manifest | 59 + toolkit/mozapps/update/tests/data/old_version.mar | Bin 0 -> 421 bytes toolkit/mozapps/update/tests/data/partial.exe | Bin 0 -> 79872 bytes toolkit/mozapps/update/tests/data/partial.mar | Bin 0 -> 10645 bytes toolkit/mozapps/update/tests/data/partial.png | Bin 0 -> 776 bytes .../update/tests/data/partial_log_failure_mac | 192 + .../update/tests/data/partial_log_failure_win | 192 + .../update/tests/data/partial_log_success_mac | 279 + .../update/tests/data/partial_log_success_win | 279 + toolkit/mozapps/update/tests/data/partial_mac.mar | Bin 0 -> 11172 bytes .../mozapps/update/tests/data/partial_precomplete | 19 + .../update/tests/data/partial_precomplete_mac | 22 + .../update/tests/data/partial_removed-files | 41 + .../update/tests/data/partial_removed-files_mac | 41 + .../update/tests/data/partial_update_manifest | 63 + .../mozapps/update/tests/data/replace_log_success | 6 + toolkit/mozapps/update/tests/data/shared.js | 632 ++ .../mozapps/update/tests/data/sharedUpdateXML.js | 364 + toolkit/mozapps/update/tests/data/simple.mar | Bin 0 -> 1031 bytes .../update/tests/data/wrong_product_channel.mar | Bin 0 -> 421 bytes .../update/tests/data/xpcshellConstantsPP.js | 53 + .../mozapps/update/tests/data/xpcshellUtilsAUS.js | 4047 +++++++++ toolkit/mozapps/update/tests/moz.build | 101 + .../update/tests/unit_aus_update/.eslintrc.js | 7 + .../canCheckForAndCanApplyUpdates.js | 138 + .../cleanupDownloadingForDifferentChannel.js | 46 + .../cleanupDownloadingForOlderAppVersion.js | 29 + .../cleanupDownloadingForSameVersionAndBuildID.js | 30 + .../cleanupDownloadingIncorrectStatus.js | 35 + .../cleanupPendingVersionFileIncorrectStatus.js | 37 + .../tests/unit_aus_update/cleanupSuccessLogMove.js | 37 + .../unit_aus_update/cleanupSuccessLogsFIFO.js | 45 + .../unit_aus_update/downloadAndHashCheckMar.js | 161 + .../downloadCompleteAfterPartialFailure.js | 66 + .../unit_aus_update/downloadInterruptedRecovery.js | 225 + .../downloadResumeForSameAppVersion.js | 37 + .../update/tests/unit_aus_update/head_update.js | 8 + .../tests/unit_aus_update/remoteUpdateXML.js | 285 + .../update/tests/unit_aus_update/uiAutoPref.js | 75 + .../update/tests/unit_aus_update/uiSilentPref.js | 76 + .../uiUnsupportedAlreadyNotified.js | 74 + .../tests/unit_aus_update/updateManagerXML.js | 177 + .../tests/unit_aus_update/urlConstruction.js | 305 + .../update/tests/unit_aus_update/xpcshell.ini | 27 + .../update/tests/unit_base_updater/.eslintrc.js | 7 + .../update/tests/unit_base_updater/head_update.js | 8 + ...invalidArgCallbackFileNotInInstallDirFailure.js | 37 + .../invalidArgCallbackFilePathTooLongFailure.js | 45 + .../invalidArgInstallDirPathTooLongFailure.js | 47 + .../invalidArgInstallDirPathTraversalFailure.js | 44 + ...idArgInstallWorkingDirPathNotSameFailure_win.js | 38 + .../invalidArgPatchDirPathTraversalFailure.js | 43 + ...invalidArgStageDirNotInInstallDirFailure_win.js | 38 + .../invalidArgWorkingDirPathLocalUNCFailure_win.js | 38 + .../invalidArgWorkingDirPathRelativeFailure.js | 37 + .../marAppApplyDirLockedStageFailure_win.js | 41 + ...marAppApplyUpdateAppBinInUseStageSuccess_win.js | 83 + .../marAppApplyUpdateStageOldVersionFailure.js | 88 + .../marAppApplyUpdateStageSuccess.js | 82 + .../unit_base_updater/marAppApplyUpdateSuccess.js | 65 + .../marAppInUseStageFailureComplete_win.js | 70 + .../marAppInUseStageSuccessComplete_unix.js | 113 + .../marAppInUseSuccessComplete.js | 62 + .../marCallbackAppStageSuccessComplete_win.js | 61 + .../marCallbackAppStageSuccessPartial_win.js | 61 + .../marCallbackAppSuccessComplete_win.js | 48 + .../marCallbackAppSuccessPartial_win.js | 48 + .../tests/unit_base_updater/marFailurePartial.js | 47 + .../marFileInUseStageFailureComplete_win.js | 67 + .../marFileInUseStageFailurePartial_win.js | 67 + .../marFileInUseSuccessComplete_win.js | 63 + .../marFileInUseSuccessPartial_win.js | 63 + .../marFileLockedFailureComplete_win.js | 57 + .../marFileLockedFailurePartial_win.js | 56 + .../marFileLockedStageFailureComplete_win.js | 71 + .../marFileLockedStageFailurePartial_win.js | 70 + .../marRMRFDirFileInUseStageFailureComplete_win.js | 72 + .../marRMRFDirFileInUseStageFailurePartial_win.js | 71 + .../marRMRFDirFileInUseSuccessComplete_win.js | 63 + .../marRMRFDirFileInUseSuccessPartial_win.js | 62 + .../unit_base_updater/marStageFailurePartial.js | 41 + .../unit_base_updater/marStageSuccessComplete.js | 132 + .../unit_base_updater/marStageSuccessPartial.js | 112 + .../tests/unit_base_updater/marSuccessComplete.js | 96 + .../tests/unit_base_updater/marSuccessPartial.js | 79 + .../tests/unit_base_updater/marVersionDowngrade.js | 51 + .../tests/unit_base_updater/marWrongChannel.js | 51 + .../update/tests/unit_base_updater/xpcshell.ini | 136 + .../update/tests/unit_service_updater/.eslintrc.js | 7 + .../tests/unit_service_updater/bootstrapSvc.js | 35 + .../unit_service_updater/checkUpdaterSigSvc.js | 39 + .../tests/unit_service_updater/head_update.js | 8 + .../invalidArgInstallDirPathTooLongFailureSvc.js | 47 + .../invalidArgInstallDirPathTraversalFailureSvc.js | 44 + ...rgInstallWorkingDirPathNotSameFailureSvc_win.js | 38 + .../invalidArgPatchDirPathTraversalFailureSvc.js | 43 + ...alidArgStageDirNotInInstallDirFailureSvc_win.js | 38 + ...validArgWorkingDirPathLocalUNCFailureSvc_win.js | 38 + .../invalidArgWorkingDirPathRelativeFailureSvc.js | 37 + .../marAppApplyDirLockedStageFailureSvc_win.js | 41 + ...AppApplyUpdateAppBinInUseStageSuccessSvc_win.js | 83 + .../marAppApplyUpdateStageSuccessSvc.js | 82 + .../marAppApplyUpdateSuccessSvc.js | 65 + .../marAppInUseStageFailureCompleteSvc_win.js | 70 + .../marAppInUseSuccessCompleteSvc.js | 62 + .../marCallbackAppStageSuccessCompleteSvc_win.js | 61 + .../marCallbackAppStageSuccessPartialSvc_win.js | 61 + .../marCallbackAppSuccessCompleteSvc_win.js | 48 + .../marCallbackAppSuccessPartialSvc_win.js | 48 + .../unit_service_updater/marFailurePartialSvc.js | 47 + .../marFileInUseStageFailureCompleteSvc_win.js | 67 + .../marFileInUseStageFailurePartialSvc_win.js | 67 + .../marFileInUseSuccessCompleteSvc_win.js | 63 + .../marFileInUseSuccessPartialSvc_win.js | 63 + .../marFileLockedFailureCompleteSvc_win.js | 57 + .../marFileLockedFailurePartialSvc_win.js | 56 + .../marFileLockedStageFailureCompleteSvc_win.js | 71 + .../marFileLockedStageFailurePartialSvc_win.js | 70 + ...rRMRFDirFileInUseStageFailureCompleteSvc_win.js | 72 + ...arRMRFDirFileInUseStageFailurePartialSvc_win.js | 71 + .../marRMRFDirFileInUseSuccessCompleteSvc_win.js | 63 + .../marRMRFDirFileInUseSuccessPartialSvc_win.js | 62 + .../marStageFailurePartialSvc.js | 41 + .../marStageSuccessCompleteSvc.js | 132 + .../marStageSuccessPartialSvc.js | 112 + .../unit_service_updater/marSuccessCompleteSvc.js | 96 + .../unit_service_updater/marSuccessPartialSvc.js | 79 + .../update/tests/unit_service_updater/xpcshell.ini | 156 + toolkit/mozapps/update/updater/Launchd.plist | 10 + toolkit/mozapps/update/updater/Makefile.in | 29 + toolkit/mozapps/update/updater/archivereader.cpp | 324 + toolkit/mozapps/update/updater/archivereader.h | 41 + .../mozapps/update/updater/automounter_gonk.cpp | 251 + toolkit/mozapps/update/updater/automounter_gonk.h | 48 + toolkit/mozapps/update/updater/bspatch.cpp | 187 + toolkit/mozapps/update/updater/bspatch.h | 93 + toolkit/mozapps/update/updater/dep1.der | Bin 0 -> 671 bytes toolkit/mozapps/update/updater/dep2.der | Bin 0 -> 671 bytes toolkit/mozapps/update/updater/gen_cert_header.py | 22 + toolkit/mozapps/update/updater/launchchild_osx.mm | 384 + toolkit/mozapps/update/updater/loaddlls.cpp | 103 + .../update/updater/macbuild/Contents/Info.plist.in | 39 + .../update/updater/macbuild/Contents/PkgInfo | 1 + .../Resources/English.lproj/InfoPlist.strings.in | 7 + .../English.lproj/MainMenu.nib/classes.nib | 19 + .../Resources/English.lproj/MainMenu.nib/info.nib | 22 + .../English.lproj/MainMenu.nib/keyedobjects.nib | Bin 0 -> 5567 bytes .../macbuild/Contents/Resources/updater.icns | Bin 0 -> 55969 bytes toolkit/mozapps/update/updater/module.ver | 1 + toolkit/mozapps/update/updater/moz.build | 62 + .../updater/nightly_aurora_level3_primary.der | Bin 0 -> 679 bytes .../updater/nightly_aurora_level3_secondary.der | Bin 0 -> 679 bytes toolkit/mozapps/update/updater/progressui.h | 40 + toolkit/mozapps/update/updater/progressui_gonk.cpp | 53 + toolkit/mozapps/update/updater/progressui_gtk.cpp | 132 + toolkit/mozapps/update/updater/progressui_null.cpp | 25 + toolkit/mozapps/update/updater/progressui_osx.mm | 144 + toolkit/mozapps/update/updater/progressui_win.cpp | 319 + toolkit/mozapps/update/updater/release_primary.der | Bin 0 -> 709 bytes .../mozapps/update/updater/release_secondary.der | Bin 0 -> 713 bytes toolkit/mozapps/update/updater/resource.h | 29 + .../mozapps/update/updater/updater-common.build | 136 + .../update/updater/updater-xpcshell/Makefile.in | 41 + .../update/updater/updater-xpcshell/moz.build | 14 + toolkit/mozapps/update/updater/updater.cpp | 4454 ++++++++++ .../update/updater/updater.exe.comctl32.manifest | 38 + .../mozapps/update/updater/updater.exe.manifest | 26 + toolkit/mozapps/update/updater/updater.ico | Bin 0 -> 92854 bytes toolkit/mozapps/update/updater/updater.png | Bin 0 -> 4030 bytes toolkit/mozapps/update/updater/updater.rc | 137 + toolkit/mozapps/update/updater/win_dirent.cpp | 78 + .../mozapps/update/updater/xpcshellCertificate.der | Bin 0 -> 677 bytes 1326 files changed, 180503 insertions(+) create mode 100644 toolkit/mozapps/downloads/DownloadLastDir.jsm create mode 100644 toolkit/mozapps/downloads/DownloadPaths.jsm create mode 100644 toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm create mode 100644 toolkit/mozapps/downloads/DownloadUtils.jsm create mode 100644 toolkit/mozapps/downloads/content/DownloadProgressListener.js create mode 100644 toolkit/mozapps/downloads/content/download.xml create mode 100644 toolkit/mozapps/downloads/content/downloads.css create mode 100644 toolkit/mozapps/downloads/content/downloads.js create mode 100644 toolkit/mozapps/downloads/content/downloads.xul create mode 100644 toolkit/mozapps/downloads/content/unknownContentType.xul create mode 100644 toolkit/mozapps/downloads/jar.mn create mode 100644 toolkit/mozapps/downloads/moz.build create mode 100644 toolkit/mozapps/downloads/nsHelperAppDlg.js create mode 100644 toolkit/mozapps/downloads/nsHelperAppDlg.manifest create mode 100644 toolkit/mozapps/downloads/tests/chrome/.eslintrc.js create mode 100644 toolkit/mozapps/downloads/tests/chrome/chrome.ini create mode 100644 toolkit/mozapps/downloads/tests/chrome/test_unknownContentType_delayedbutton.xul create mode 100644 toolkit/mozapps/downloads/tests/chrome/test_unknownContentType_dialog_layout.xul create mode 100644 toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif create mode 100644 toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^ create mode 100644 toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt create mode 100644 toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt^headers^ create mode 100644 toolkit/mozapps/downloads/tests/moz.build create mode 100644 toolkit/mozapps/downloads/tests/unit/.eslintrc.js create mode 100644 toolkit/mozapps/downloads/tests/unit/head_downloads.js create mode 100644 toolkit/mozapps/downloads/tests/unit/test_DownloadPaths.js create mode 100644 toolkit/mozapps/downloads/tests/unit/test_DownloadUtils.js create mode 100644 toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js create mode 100644 toolkit/mozapps/downloads/tests/unit/test_syncedDownloadUtils.js create mode 100644 toolkit/mozapps/downloads/tests/unit/test_unspecified_arguments.js create mode 100644 toolkit/mozapps/downloads/tests/unit/xpcshell.ini create mode 100644 toolkit/mozapps/extensions/.eslintrc.js create mode 100644 toolkit/mozapps/extensions/AddonContentPolicy.cpp create mode 100644 toolkit/mozapps/extensions/AddonContentPolicy.h create mode 100644 toolkit/mozapps/extensions/AddonManager.jsm create mode 100644 toolkit/mozapps/extensions/AddonManagerWebAPI.cpp create mode 100644 toolkit/mozapps/extensions/AddonManagerWebAPI.h create mode 100644 toolkit/mozapps/extensions/AddonPathService.cpp create mode 100644 toolkit/mozapps/extensions/AddonPathService.h create mode 100644 toolkit/mozapps/extensions/ChromeManifestParser.jsm create mode 100644 toolkit/mozapps/extensions/DeferredSave.jsm create mode 100644 toolkit/mozapps/extensions/LightweightThemeManager.jsm create mode 100644 toolkit/mozapps/extensions/addonManager.js create mode 100644 toolkit/mozapps/extensions/amContentHandler.js create mode 100644 toolkit/mozapps/extensions/amIAddonManager.idl create mode 100644 toolkit/mozapps/extensions/amIAddonPathService.idl create mode 100644 toolkit/mozapps/extensions/amIWebInstallListener.idl create mode 100644 toolkit/mozapps/extensions/amIWebInstaller.idl create mode 100644 toolkit/mozapps/extensions/amInstallTrigger.js create mode 100644 toolkit/mozapps/extensions/amWebAPI.js create mode 100644 toolkit/mozapps/extensions/amWebInstallListener.js create mode 100644 toolkit/mozapps/extensions/content/OpenH264-license.txt create mode 100644 toolkit/mozapps/extensions/content/about.js create mode 100644 toolkit/mozapps/extensions/content/about.xul create mode 100644 toolkit/mozapps/extensions/content/blocklist.css create mode 100644 toolkit/mozapps/extensions/content/blocklist.js create mode 100644 toolkit/mozapps/extensions/content/blocklist.xml create mode 100644 toolkit/mozapps/extensions/content/blocklist.xul create mode 100644 toolkit/mozapps/extensions/content/eula.js create mode 100644 toolkit/mozapps/extensions/content/eula.xul create mode 100644 toolkit/mozapps/extensions/content/extensions.css create mode 100644 toolkit/mozapps/extensions/content/extensions.js create mode 100644 toolkit/mozapps/extensions/content/extensions.xml create mode 100644 toolkit/mozapps/extensions/content/extensions.xul create mode 100644 toolkit/mozapps/extensions/content/gmpPrefs.xul create mode 100644 toolkit/mozapps/extensions/content/list.js create mode 100644 toolkit/mozapps/extensions/content/list.xul create mode 100644 toolkit/mozapps/extensions/content/newaddon.js create mode 100644 toolkit/mozapps/extensions/content/newaddon.xul create mode 100644 toolkit/mozapps/extensions/content/pluginPrefs.xul create mode 100644 toolkit/mozapps/extensions/content/setting.xml create mode 100644 toolkit/mozapps/extensions/content/update.js create mode 100644 toolkit/mozapps/extensions/content/update.xul create mode 100644 toolkit/mozapps/extensions/content/updateinfo.xsl create mode 100644 toolkit/mozapps/extensions/content/xpinstallConfirm.css create mode 100644 toolkit/mozapps/extensions/content/xpinstallConfirm.js create mode 100644 toolkit/mozapps/extensions/content/xpinstallConfirm.xul create mode 100644 toolkit/mozapps/extensions/content/xpinstallItem.xml create mode 100644 toolkit/mozapps/extensions/docs/SystemAddons.rst create mode 100644 toolkit/mozapps/extensions/docs/index.rst create mode 100644 toolkit/mozapps/extensions/extensions.manifest create mode 100644 toolkit/mozapps/extensions/internal/APIExtensionBootstrap.js create mode 100644 toolkit/mozapps/extensions/internal/AddonConstants.jsm create mode 100644 toolkit/mozapps/extensions/internal/AddonLogging.jsm create mode 100644 toolkit/mozapps/extensions/internal/AddonRepository.jsm create mode 100644 toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm create mode 100644 toolkit/mozapps/extensions/internal/AddonTestUtils.jsm create mode 100644 toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm create mode 100644 toolkit/mozapps/extensions/internal/Content.js create mode 100644 toolkit/mozapps/extensions/internal/E10SAddonsRollout.jsm create mode 100644 toolkit/mozapps/extensions/internal/GMPProvider.jsm create mode 100644 toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm create mode 100644 toolkit/mozapps/extensions/internal/PluginProvider.jsm create mode 100644 toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm create mode 100644 toolkit/mozapps/extensions/internal/SpellCheckDictionaryBootstrap.js create mode 100644 toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js create mode 100644 toolkit/mozapps/extensions/internal/XPIProvider.jsm create mode 100644 toolkit/mozapps/extensions/internal/XPIProviderUtils.js create mode 100644 toolkit/mozapps/extensions/internal/moz.build create mode 100644 toolkit/mozapps/extensions/jar.mn create mode 100644 toolkit/mozapps/extensions/moz.build create mode 100644 toolkit/mozapps/extensions/nsBlocklistService.js create mode 100644 toolkit/mozapps/extensions/nsBlocklistServiceContent.js create mode 100644 toolkit/mozapps/extensions/test/AddonManagerTesting.jsm create mode 100644 toolkit/mozapps/extensions/test/Makefile.in create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_hard1_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_hard1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_hard1_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_regexp1_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_regexp1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_regexp1_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft1_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft1_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft2_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft2_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft2_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft3_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft3_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft3_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft4_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft4_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft4_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft5_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft5_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft5_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/bootstrap_globals/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/bootstrap_globals/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/min1max1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/min1max2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/min1max3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/min1max3b/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/override1x2-1x3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_3/icon.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_3/preview.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/version.jsm create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_2/version.jsm create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_3/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_3/version.jsm create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap2_1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap2_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap_const/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap_const/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_a_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_a_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_b_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_b_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_c_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_c_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_d_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_d_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_e_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_e_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_f_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_f_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_g_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_g_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_5/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_6/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_7/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_8/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_9/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug371495/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug394300_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug394300_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug397778/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug425657/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_5/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug521905/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug567173/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug567184/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug567184/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_1/testfile create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_1/testfile1 create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_2/testfile create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_2/testfile2 create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug594058/directory/file1 create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug594058/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug595573/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug655254/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug655254_2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug655254_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug659772/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug675371/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug675371/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug675371/test.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_bug757663/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_cacheflush1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_cacheflush2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_1/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_2/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/inner.jar create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/components/components.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/components/other/something.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_5/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_5/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_6/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_6/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_data_directory/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_db_sanity_1_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_db_sanity_1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_complete_v2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_complete_v2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_complete_webextension_v2/manifest.json create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_defer_v2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_defer_v2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_defer_webextension_v2/manifest.json create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_ignore_v2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_ignore_v2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_ignore_webextension_v2/manifest.json create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary/dictionaries/ab-CD.dic create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_2/dictionaries/ab-CD.dic create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_5/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_distribution1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_experiment1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_experiment1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_filepointer/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_getresource/icon.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_getresource/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_getresource/subdir/subfile.txt create mode 100644 toolkit/mozapps/extensions/test/addons/test_hotfix_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_hotfix_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install1/icon.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_install1/icon64.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_install1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install2_1/icon.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_install2_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install2_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon4.xpi create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon5.jar create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon6.xpi create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon7.jar create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/badaddon.jar create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/badaddon.xpi create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/icon.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install5/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_install5/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install6/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install7/addon1.xpi create mode 100644 toolkit/mozapps/extensions/test/addons/test_install7/addon2.xpi create mode 100644 toolkit/mozapps/extensions/test/addons/test_install7/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_install8/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_jetpack/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_jetpack/harness-options.json create mode 100644 toolkit/mozapps/extensions/test/addons/test_jetpack/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_langpack/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_langpack/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_locale/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_locked2_5/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_locked2_6/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate4_6/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate4_7/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate6/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate7/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate8/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate8/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate9/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_symbol/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_symbol/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_theme/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_theme/preview.png create mode 100644 toolkit/mozapps/extensions/test/addons/test_undoincompatible/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_undoincompatible/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_undouninstall1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_undouninstall1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_update/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_update12/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_update8/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi2/addon.xpi create mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/upgradeable1x2-3_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/upgradeable1x2-3_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/webextension_1/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/addons/webextension_1/manifest.json create mode 100644 toolkit/mozapps/extensions/test/addons/webextension_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/addons/webextension_2/manifest.json create mode 100644 toolkit/mozapps/extensions/test/addons/webextension_3/_locales/en/messages.json create mode 100644 toolkit/mozapps/extensions/test/addons/webextension_3/_locales/fr/messages.json create mode 100644 toolkit/mozapps/extensions/test/addons/webextension_3/manifest.json create mode 100644 toolkit/mozapps/extensions/test/browser/.eslintrc.js create mode 100644 toolkit/mozapps/extensions/test/browser/addon_about.xul create mode 100644 toolkit/mozapps/extensions/test/browser/addon_prefs.xul create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_10.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_10/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_3.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_3/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_4.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_4/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_5.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_5/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_6.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_6/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_7.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_7/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_8_1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_8_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_9_1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_9_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_experiment1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_experiment1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/options.xul create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/settings.dtd create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/binding.xml create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/options.xul create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/string.dtd create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info/options.xul create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_installssl.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_installssl/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_searching.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_searching/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_searching/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/frame-script.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/chrome.manifest create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/frame-script.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install/install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/addons/options_signed.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/addons/options_signed/manifest.json create mode 100644 toolkit/mozapps/extensions/test/browser/addons/options_signed/options.html create mode 100644 toolkit/mozapps/extensions/test/browser/blockNoPlugins.xml create mode 100644 toolkit/mozapps/extensions/test/browser/blockPluginHard.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser-common.ini create mode 100644 toolkit/mozapps/extensions/test/browser/browser-window.ini create mode 100644 toolkit/mozapps/extensions/test/browser/browser.ini create mode 100644 toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_about.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_addonrepository_performance.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug523784.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557943.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956_8_2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956_9_2.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562797.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562854.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562890.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562899.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562992.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug567127.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug567137.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug570760.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug572561.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug573062.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug577990.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug580298.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug581076.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug586574.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug587970.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug590347.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug591465.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug591465.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug591663.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug593535.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug593535.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug596336.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug608316.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug610764.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug616841.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug618502.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug679604.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug714593.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_cancelCompatCheck.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_checkAddonCompatibility.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_details.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_discovery.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_discovery_install.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_dragdrop.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_eula.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_eula.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_experiments.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_globalwarnings.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_hotfix.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings_custom.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings_info.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.rdf^headers^ create mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_install1_3.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/browser_installssl.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_list.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_manualupdates.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_metadataTimeout.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_newaddon.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_openDialog.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_pluginprefs.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_purchase.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_purchase.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_recentupdates.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_searching.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_searching.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_searching_empty.xml create mode 100644 toolkit/mozapps/extensions/test/browser/browser_sorting.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_sorting_plugins.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_system_addons_are_e10s.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_tabsettings.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_task_next_test.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_types.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_uninstalling.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_update.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_updateid.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_updatessl.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_updatessl.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/browser_updatessl.rdf^headers^ create mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_access.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_addon_listener.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_enable.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_install.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_uninstall.js create mode 100644 toolkit/mozapps/extensions/test/browser/browser_webext_options.js create mode 100644 toolkit/mozapps/extensions/test/browser/cancelCompatCheck.sjs create mode 100644 toolkit/mozapps/extensions/test/browser/discovery.html create mode 100644 toolkit/mozapps/extensions/test/browser/discovery_frame.html create mode 100644 toolkit/mozapps/extensions/test/browser/discovery_install.html create mode 100644 toolkit/mozapps/extensions/test/browser/head.js create mode 100644 toolkit/mozapps/extensions/test/browser/more_options.xul create mode 100644 toolkit/mozapps/extensions/test/browser/moz.build create mode 100644 toolkit/mozapps/extensions/test/browser/options.xul create mode 100644 toolkit/mozapps/extensions/test/browser/plugin_test.html create mode 100644 toolkit/mozapps/extensions/test/browser/redirect.sjs create mode 100644 toolkit/mozapps/extensions/test/browser/releaseNotes.xhtml create mode 100644 toolkit/mozapps/extensions/test/browser/signed_hotfix.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/signed_hotfix.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/unsigned_hotfix.rdf create mode 100644 toolkit/mozapps/extensions/test/browser/unsigned_hotfix.xpi create mode 100644 toolkit/mozapps/extensions/test/browser/webapi_addon_listener.html create mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html create mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checkchromeframe.xul create mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checkframed.html create mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checknavigatedwindow.html create mode 100644 toolkit/mozapps/extensions/test/mochitest/.eslintrc.js create mode 100644 toolkit/mozapps/extensions/test/mochitest/file_bug687194.xpi create mode 100644 toolkit/mozapps/extensions/test/mochitest/file_empty.html create mode 100644 toolkit/mozapps/extensions/test/mochitest/mochitest.ini create mode 100644 toolkit/mozapps/extensions/test/mochitest/test_bug609794.html create mode 100644 toolkit/mozapps/extensions/test/mochitest/test_bug687194.html create mode 100644 toolkit/mozapps/extensions/test/mochitest/test_bug887098.html create mode 100644 toolkit/mozapps/extensions/test/moz.build create mode 100644 toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_change.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update1.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update2.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update3.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/app_update.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/blocklist_update1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/blocklist_update2.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/manual_update.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_empty.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_start.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_warn.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/corrupt.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/corruptfile.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/empty.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/from_sources/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/from_sources/install.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/pluginInfoURL_block.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.txt create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad2.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/empty.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/missing.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/unsigned.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/test.txt create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/test.txt create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/hotfix_badid.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/hotfix_broken.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/hotfix_good.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_63_hash.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_63_plain.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_64_hash.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_64_plain.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_65_hash.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_badid.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_broken.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_signed.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_unsigned.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_1/install.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_1/test.txt create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_2/test.txt create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/preliminary_bootstrap_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_bootstrap_1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_bootstrap_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_bootstrap_badid_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_nonbootstrap_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_nonbootstrap_badid_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned_bootstrap_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned_nonbootstrap_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_1_badcert.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system2_1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system2_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system2_3.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system3_1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system3_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system3_3.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system4_1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system5_1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system6_1_unpack.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system6_2_notBootstrap.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system6_3_notMultiprocess.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_complete.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_complete_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer_also.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer_also_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_failed_update.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_compatmode_ignore.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_compatmode_normal.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_compatmode_strict.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_empty.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_failed.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_prefs_1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_regexp_1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug299716.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug299716_2.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug324121.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug394300.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug424262.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug468528.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_1.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_2.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_3.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_4.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_5.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_1.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_2.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_3.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_4.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_5.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_2.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_empty.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_2.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug526598_1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug526598_2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug541420.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug542391.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug554133.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug619730.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_compatoverrides.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_corrupt.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_complete/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_defer/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_ignore/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_dictionary.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/install.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/dummy.txt create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/subdir2/dummy2.txt create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist2.xml create mode 100755 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_OSVersion.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_hotfix_1.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_hotfix_2.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_hotfix_3.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_install.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_install.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_migrate4.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_no_update.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/ancient.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/new.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/old.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtp.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtpUndo.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_proxy/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_softblocked1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_sourceURI.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_temporary/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update_multi.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_ignore.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_normal.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_strict.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updateid.rdf create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/unsigned.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/webext-implicit-id.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_addons.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_unpack.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_compatmode.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_XPIcancel.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_addon_path_service.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_badschema.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_metadata_filters.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_prefs.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_regexp.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_const.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_globals.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_resource.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug1180901.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug1180901_2.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug299716.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug299716_2.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug393285.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug394300.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug406118.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug424262.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug430120.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug449027.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug465190.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug468528.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_1.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_1_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_2.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug514327_1.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug514327_2.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug514327_3.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug554133.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug594058.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug595081.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug619730.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug620837.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug953156.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_cache_certdb.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_checkCompatibility_themeOverride.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_childprocess.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_corruptfile.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dataDirectory.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_delay_update.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dependencies.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_disable.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_distribution.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dss.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_error.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_experiment.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_fuel.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_general.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_getresource.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Device.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_DriverNew.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverNew.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverOld.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_OK.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_DriverOld.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_OK.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_No_Comparison.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OK.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OS.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_match.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Vendor.js create mode 100755 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Version.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_prefs.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_hotfix_cert.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_from_sources.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_isDebuggable.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_isReady.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_langpack.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_locale.js create mode 100755 toolkit/mozapps/extensions/test/xpcshell/test_locked.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_locked2.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_manifest.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_multiprocessCompatible.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_nodisable_hidden.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pass_symbol.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_permissions.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pluginInfoURL.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_plugins.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pref_properties.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_markSafe.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_shutdown.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_unsafe_access_shutdown.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_unsafe_access_startup.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_proxies.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_proxy.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_registry.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_reload.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_safemode.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_schema_change.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_seen.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_seen_newprofile.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_long.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_multi.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_updatepref.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_softblocked.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_sourceURI.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_startup.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_switch_os.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_temporary.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_theme.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_types.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updateCancel.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_compatmode.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_webextensions.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updateid.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_paths.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini create mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini create mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini create mode 100644 toolkit/mozapps/extensions/test/xpinstall/.eslintrc.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/amosigned2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/authRedirect.sjs create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser.ini create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger_iframe.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_url.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth4.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badhash.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badhashtype.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug540558.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug611242.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug645699.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug672485.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_concurrent_installs.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_corrupt.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_datauri.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_empty.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_enabled.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_enabled2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_hash.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_hash2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash4.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash5.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_installchrome.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_multipackage.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway4.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_offline.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_relative.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_multipackage.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_multiple.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_naming.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_tampered.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_trigger.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_untrusted.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_url.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_softwareupdate.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_switchtab.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_trigger_redirect.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_iframe.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_xorigin.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_url.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist2.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist3.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist4.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist5.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist6.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/bug540558.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/bug638292.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/bug645699.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/concurrent_installs.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs create mode 100644 toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/empty.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/enabled.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs create mode 100644 toolkit/mozapps/extensions/test/xpinstall/head.js create mode 100644 toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/installchrome.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/installtrigger.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/navigate.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/redirect.sjs create mode 100644 toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/restartless.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs create mode 100644 toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/theme.xpi create mode 100644 toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html create mode 100644 toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi create mode 100644 toolkit/mozapps/handling/content/dialog.js create mode 100644 toolkit/mozapps/handling/content/dialog.xul create mode 100644 toolkit/mozapps/handling/content/handler.css create mode 100644 toolkit/mozapps/handling/content/handler.xml create mode 100644 toolkit/mozapps/handling/jar.mn create mode 100644 toolkit/mozapps/handling/moz.build create mode 100644 toolkit/mozapps/handling/nsContentDispatchChooser.js create mode 100644 toolkit/mozapps/handling/nsContentDispatchChooser.manifest create mode 100644 toolkit/mozapps/installer/find-dupes.py create mode 100644 toolkit/mozapps/installer/informulate.py create mode 100644 toolkit/mozapps/installer/js-compare-ast.js create mode 100644 toolkit/mozapps/installer/l10n-repack.py create mode 100644 toolkit/mozapps/installer/linux/rpm/mozilla.desktop create mode 100644 toolkit/mozapps/installer/linux/rpm/mozilla.spec create mode 100644 toolkit/mozapps/installer/make-eme.mk create mode 100644 toolkit/mozapps/installer/package-name.mk create mode 100644 toolkit/mozapps/installer/packager.mk create mode 100644 toolkit/mozapps/installer/packager.py create mode 100644 toolkit/mozapps/installer/precompile_cache.js create mode 100644 toolkit/mozapps/installer/signing.mk create mode 100644 toolkit/mozapps/installer/strip.py create mode 100644 toolkit/mozapps/installer/unpack.py create mode 100644 toolkit/mozapps/installer/upload-files-APK.mk create mode 100644 toolkit/mozapps/installer/upload-files.mk create mode 100755 toolkit/mozapps/installer/windows/nsis/common.nsh create mode 100644 toolkit/mozapps/installer/windows/nsis/locale-fonts.nsh create mode 100644 toolkit/mozapps/installer/windows/nsis/locale-rtl.nlf create mode 100644 toolkit/mozapps/installer/windows/nsis/locale.nlf create mode 100755 toolkit/mozapps/installer/windows/nsis/locales.nsi create mode 100644 toolkit/mozapps/installer/windows/nsis/makensis.mk create mode 100755 toolkit/mozapps/installer/windows/nsis/overrides.nsh create mode 100644 toolkit/mozapps/installer/windows/nsis/preprocess-locale.py create mode 100644 toolkit/mozapps/installer/windows/nsis/setup.ico create mode 100644 toolkit/mozapps/preferences/changemp.js create mode 100644 toolkit/mozapps/preferences/changemp.xul create mode 100644 toolkit/mozapps/preferences/fontbuilder.js create mode 100644 toolkit/mozapps/preferences/jar.mn create mode 100644 toolkit/mozapps/preferences/moz.build create mode 100644 toolkit/mozapps/preferences/removemp.js create mode 100644 toolkit/mozapps/preferences/removemp.xul create mode 100644 toolkit/mozapps/update/UpdateTelemetry.jsm create mode 100644 toolkit/mozapps/update/common-standalone/moz.build create mode 100644 toolkit/mozapps/update/common/certificatecheck.cpp create mode 100644 toolkit/mozapps/update/common/certificatecheck.h create mode 100644 toolkit/mozapps/update/common/errors.h create mode 100644 toolkit/mozapps/update/common/moz.build create mode 100644 toolkit/mozapps/update/common/pathhash.cpp create mode 100644 toolkit/mozapps/update/common/pathhash.h create mode 100644 toolkit/mozapps/update/common/readstrings.cpp create mode 100644 toolkit/mozapps/update/common/readstrings.h create mode 100644 toolkit/mozapps/update/common/registrycertificates.cpp create mode 100644 toolkit/mozapps/update/common/registrycertificates.h create mode 100644 toolkit/mozapps/update/common/sources.mozbuild create mode 100644 toolkit/mozapps/update/common/uachelper.cpp create mode 100644 toolkit/mozapps/update/common/uachelper.h create mode 100644 toolkit/mozapps/update/common/updatecommon.cpp create mode 100644 toolkit/mozapps/update/common/updatecommon.h create mode 100644 toolkit/mozapps/update/common/updatedefines.h create mode 100644 toolkit/mozapps/update/common/updatehelper.cpp create mode 100644 toolkit/mozapps/update/common/updatehelper.h create mode 100644 toolkit/mozapps/update/common/win_dirent.h create mode 100644 toolkit/mozapps/update/content/history.js create mode 100644 toolkit/mozapps/update/content/history.xul create mode 100644 toolkit/mozapps/update/content/updates.css create mode 100644 toolkit/mozapps/update/content/updates.js create mode 100644 toolkit/mozapps/update/content/updates.xml create mode 100644 toolkit/mozapps/update/content/updates.xul create mode 100644 toolkit/mozapps/update/jar.mn create mode 100644 toolkit/mozapps/update/moz.build create mode 100644 toolkit/mozapps/update/nsIUpdateService.idl create mode 100644 toolkit/mozapps/update/nsUpdateService.js create mode 100644 toolkit/mozapps/update/nsUpdateService.manifest create mode 100644 toolkit/mozapps/update/nsUpdateServiceStub.js create mode 100644 toolkit/mozapps/update/tests/Makefile.in create mode 100644 toolkit/mozapps/update/tests/TestAUSHelper.cpp create mode 100644 toolkit/mozapps/update/tests/TestAUSReadStrings.cpp create mode 100644 toolkit/mozapps/update/tests/TestAUSReadStrings1.ini create mode 100644 toolkit/mozapps/update/tests/TestAUSReadStrings2.ini create mode 100644 toolkit/mozapps/update/tests/TestAUSReadStrings3.ini create mode 100644 toolkit/mozapps/update/tests/chrome/.eslintrc.js create mode 100644 toolkit/mozapps/update/tests/chrome/chrome.ini create mode 100644 toolkit/mozapps/update/tests/chrome/test_0010_background_basic.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul create mode 100644 toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul create mode 100644 toolkit/mozapps/update/tests/chrome/update.sjs create mode 100644 toolkit/mozapps/update/tests/chrome/utils.js create mode 100644 toolkit/mozapps/update/tests/data/complete.exe create mode 100644 toolkit/mozapps/update/tests/data/complete.mar create mode 100644 toolkit/mozapps/update/tests/data/complete.png create mode 100644 toolkit/mozapps/update/tests/data/complete_log_success_mac create mode 100644 toolkit/mozapps/update/tests/data/complete_log_success_win create mode 100644 toolkit/mozapps/update/tests/data/complete_mac.mar create mode 100644 toolkit/mozapps/update/tests/data/complete_precomplete create mode 100644 toolkit/mozapps/update/tests/data/complete_precomplete_mac create mode 100644 toolkit/mozapps/update/tests/data/complete_removed-files create mode 100644 toolkit/mozapps/update/tests/data/complete_removed-files_mac create mode 100644 toolkit/mozapps/update/tests/data/complete_update_manifest create mode 100644 toolkit/mozapps/update/tests/data/old_version.mar create mode 100644 toolkit/mozapps/update/tests/data/partial.exe create mode 100644 toolkit/mozapps/update/tests/data/partial.mar create mode 100644 toolkit/mozapps/update/tests/data/partial.png create mode 100644 toolkit/mozapps/update/tests/data/partial_log_failure_mac create mode 100644 toolkit/mozapps/update/tests/data/partial_log_failure_win create mode 100644 toolkit/mozapps/update/tests/data/partial_log_success_mac create mode 100644 toolkit/mozapps/update/tests/data/partial_log_success_win create mode 100644 toolkit/mozapps/update/tests/data/partial_mac.mar create mode 100644 toolkit/mozapps/update/tests/data/partial_precomplete create mode 100644 toolkit/mozapps/update/tests/data/partial_precomplete_mac create mode 100644 toolkit/mozapps/update/tests/data/partial_removed-files create mode 100644 toolkit/mozapps/update/tests/data/partial_removed-files_mac create mode 100644 toolkit/mozapps/update/tests/data/partial_update_manifest create mode 100644 toolkit/mozapps/update/tests/data/replace_log_success create mode 100644 toolkit/mozapps/update/tests/data/shared.js create mode 100644 toolkit/mozapps/update/tests/data/sharedUpdateXML.js create mode 100644 toolkit/mozapps/update/tests/data/simple.mar create mode 100644 toolkit/mozapps/update/tests/data/wrong_product_channel.mar create mode 100644 toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js create mode 100644 toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js create mode 100644 toolkit/mozapps/update/tests/moz.build create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/head_update.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/head_update.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js create mode 100644 toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/head_update.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js create mode 100644 toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini create mode 100644 toolkit/mozapps/update/updater/Launchd.plist create mode 100644 toolkit/mozapps/update/updater/Makefile.in create mode 100644 toolkit/mozapps/update/updater/archivereader.cpp create mode 100644 toolkit/mozapps/update/updater/archivereader.h create mode 100644 toolkit/mozapps/update/updater/automounter_gonk.cpp create mode 100644 toolkit/mozapps/update/updater/automounter_gonk.h create mode 100644 toolkit/mozapps/update/updater/bspatch.cpp create mode 100644 toolkit/mozapps/update/updater/bspatch.h create mode 100644 toolkit/mozapps/update/updater/dep1.der create mode 100644 toolkit/mozapps/update/updater/dep2.der create mode 100644 toolkit/mozapps/update/updater/gen_cert_header.py create mode 100644 toolkit/mozapps/update/updater/launchchild_osx.mm create mode 100644 toolkit/mozapps/update/updater/loaddlls.cpp create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns create mode 100644 toolkit/mozapps/update/updater/module.ver create mode 100644 toolkit/mozapps/update/updater/moz.build create mode 100644 toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der create mode 100644 toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der create mode 100644 toolkit/mozapps/update/updater/progressui.h create mode 100644 toolkit/mozapps/update/updater/progressui_gonk.cpp create mode 100644 toolkit/mozapps/update/updater/progressui_gtk.cpp create mode 100644 toolkit/mozapps/update/updater/progressui_null.cpp create mode 100644 toolkit/mozapps/update/updater/progressui_osx.mm create mode 100644 toolkit/mozapps/update/updater/progressui_win.cpp create mode 100644 toolkit/mozapps/update/updater/release_primary.der create mode 100644 toolkit/mozapps/update/updater/release_secondary.der create mode 100644 toolkit/mozapps/update/updater/resource.h create mode 100644 toolkit/mozapps/update/updater/updater-common.build create mode 100644 toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in create mode 100644 toolkit/mozapps/update/updater/updater-xpcshell/moz.build create mode 100644 toolkit/mozapps/update/updater/updater.cpp create mode 100644 toolkit/mozapps/update/updater/updater.exe.comctl32.manifest create mode 100644 toolkit/mozapps/update/updater/updater.exe.manifest create mode 100644 toolkit/mozapps/update/updater/updater.ico create mode 100644 toolkit/mozapps/update/updater/updater.png create mode 100644 toolkit/mozapps/update/updater/updater.rc create mode 100644 toolkit/mozapps/update/updater/win_dirent.cpp create mode 100644 toolkit/mozapps/update/updater/xpcshellCertificate.der (limited to 'toolkit/mozapps') diff --git a/toolkit/mozapps/downloads/DownloadLastDir.jsm b/toolkit/mozapps/downloads/DownloadLastDir.jsm new file mode 100644 index 000000000..552fd3ef1 --- /dev/null +++ b/toolkit/mozapps/downloads/DownloadLastDir.jsm @@ -0,0 +1,195 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The behavior implemented by gDownloadLastDir is documented here. + * + * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir + * preference to store the last used download directory. The first time the user + * switches into the private browsing mode, the last download directory is + * preserved to the pref value, but if the user switches to another directory + * during the private browsing mode, that directory is not stored in the pref, + * and will be merely kept in memory. When leaving the private browsing mode, + * this in-memory value will be discarded, and the last download directory + * will be reverted to the pref value. + * + * Both the pref and the in-memory value will be cleared when clearing the + * browsing history. This effectively changes the last download directory + * to the default download directory on each platform. + * + * If passed a URI, the last used directory is also stored with that URI in the + * content preferences database. This can be disabled by setting the pref + * browser.download.lastDir.savePerSite to false. + */ + +const LAST_DIR_PREF = "browser.download.lastDir"; +const SAVE_PER_SITE_PREF = LAST_DIR_PREF + ".savePerSite"; +const nsIFile = Components.interfaces.nsIFile; + +this.EXPORTED_SYMBOLS = [ "DownloadLastDir" ]; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); + +var observer = { + QueryInterface: function (aIID) { + if (aIID.equals(Components.interfaces.nsIObserver) || + aIID.equals(Components.interfaces.nsISupports) || + aIID.equals(Components.interfaces.nsISupportsWeakReference)) + return this; + throw Components.results.NS_NOINTERFACE; + }, + observe: function (aSubject, aTopic, aData) { + switch (aTopic) { + case "last-pb-context-exited": + gDownloadLastDirFile = null; + break; + case "browser:purge-session-history": + gDownloadLastDirFile = null; + if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) + Services.prefs.clearUserPref(LAST_DIR_PREF); + // Ensure that purging session history causes both the session-only PB cache + // and persistent prefs to be cleared. + let cps2 = Components.classes["@mozilla.org/content-pref/service;1"]. + getService(Components.interfaces.nsIContentPrefService2); + + cps2.removeByName(LAST_DIR_PREF, {usePrivateBrowsing: false}); + cps2.removeByName(LAST_DIR_PREF, {usePrivateBrowsing: true}); + break; + } + } +}; + +var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); +os.addObserver(observer, "last-pb-context-exited", true); +os.addObserver(observer, "browser:purge-session-history", true); + +function readLastDirPref() { + try { + return Services.prefs.getComplexValue(LAST_DIR_PREF, nsIFile); + } + catch (e) { + return null; + } +} + +function isContentPrefEnabled() { + try { + return Services.prefs.getBoolPref(SAVE_PER_SITE_PREF); + } + catch (e) { + return true; + } +} + +var gDownloadLastDirFile = readLastDirPref(); + +this.DownloadLastDir = function DownloadLastDir(aWindow) { + let loadContext = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsILoadContext); + // Need this in case the real thing has gone away by the time we need it. + // We only care about the private browsing state. All the rest of the + // load context isn't of interest to the content pref service. + this.fakeContext = { + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsILoadContext]), + usePrivateBrowsing: loadContext.usePrivateBrowsing, + originAttributes: {}, + }; +} + +DownloadLastDir.prototype = { + isPrivate: function DownloadLastDir_isPrivate() { + return this.fakeContext.usePrivateBrowsing; + }, + // compat shims + get file() { return this._getLastFile(); }, + set file(val) { this.setFile(null, val); }, + cleanupPrivateFile: function () { + gDownloadLastDirFile = null; + }, + // This function is now deprecated as it uses the sync nsIContentPrefService + // interface. New consumers should use the getFileAsync function. + getFile: function (aURI) { + let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated; + Deprecated.warning("DownloadLastDir.getFile is deprecated. Please use getFileAsync instead.", + "https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/DownloadLastDir.jsm", + Components.stack.caller); + + if (aURI && isContentPrefEnabled()) { + let lastDir = Services.contentPrefs.getPref(aURI, LAST_DIR_PREF, this.fakeContext); + if (lastDir) { + var lastDirFile = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsIFile); + lastDirFile.initWithPath(lastDir); + return lastDirFile; + } + } + return this._getLastFile(); + }, + + _getLastFile: function () { + if (gDownloadLastDirFile && !gDownloadLastDirFile.exists()) + gDownloadLastDirFile = null; + + if (this.isPrivate()) { + if (!gDownloadLastDirFile) + gDownloadLastDirFile = readLastDirPref(); + return gDownloadLastDirFile; + } + return readLastDirPref(); + }, + + getFileAsync: function(aURI, aCallback) { + let plainPrefFile = this._getLastFile(); + if (!aURI || !isContentPrefEnabled()) { + Services.tm.mainThread.dispatch(() => aCallback(plainPrefFile), + Components.interfaces.nsIThread.DISPATCH_NORMAL); + return; + } + + let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI; + let cps2 = Components.classes["@mozilla.org/content-pref/service;1"] + .getService(Components.interfaces.nsIContentPrefService2); + let result = null; + cps2.getByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext, { + handleResult: aResult => result = aResult, + handleCompletion: function(aReason) { + let file = plainPrefFile; + if (aReason == Components.interfaces.nsIContentPrefCallback2.COMPLETE_OK && + result instanceof Components.interfaces.nsIContentPref) { + file = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsIFile); + file.initWithPath(result.value); + } + aCallback(file); + } + }); + }, + + setFile: function (aURI, aFile) { + if (aURI && isContentPrefEnabled()) { + let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI; + let cps2 = Components.classes["@mozilla.org/content-pref/service;1"] + .getService(Components.interfaces.nsIContentPrefService2); + if (aFile instanceof Components.interfaces.nsIFile) + cps2.set(uri, LAST_DIR_PREF, aFile.path, this.fakeContext); + else + cps2.removeByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext); + } + if (this.isPrivate()) { + if (aFile instanceof Components.interfaces.nsIFile) + gDownloadLastDirFile = aFile.clone(); + else + gDownloadLastDirFile = null; + } else if (aFile instanceof Components.interfaces.nsIFile) { + Services.prefs.setComplexValue(LAST_DIR_PREF, nsIFile, aFile); + } else if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) { + Services.prefs.clearUserPref(LAST_DIR_PREF); + } + } +}; diff --git a/toolkit/mozapps/downloads/DownloadPaths.jsm b/toolkit/mozapps/downloads/DownloadPaths.jsm new file mode 100644 index 000000000..6ca6dfc13 --- /dev/null +++ b/toolkit/mozapps/downloads/DownloadPaths.jsm @@ -0,0 +1,89 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +this.EXPORTED_SYMBOLS = [ + "DownloadPaths", +]; + +/** + * This module provides the DownloadPaths object which contains methods for + * giving names and paths to files being downloaded. + * + * List of methods: + * + * nsILocalFile + * createNiceUniqueFile(nsILocalFile aLocalFile) + * + * [string base, string ext] + * splitBaseNameAndExtension(string aLeafName) + */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +this.DownloadPaths = { + /** + * Creates a uniquely-named file starting from the name of the provided file. + * If a file with the provided name already exists, the function attempts to + * create nice alternatives, like "base(1).ext" (instead of "base-1.ext"). + * + * If a unique name cannot be found, the function throws the XPCOM exception + * NS_ERROR_FILE_TOO_BIG. Other exceptions, like NS_ERROR_FILE_ACCESS_DENIED, + * can also be expected. + * + * @param aTemplateFile + * nsILocalFile whose leaf name is going to be used as a template. The + * provided object is not modified. + * @returns A new instance of an nsILocalFile object pointing to the newly + * created empty file. On platforms that support permission bits, the + * file is created with permissions 644. + */ + createNiceUniqueFile: function DP_createNiceUniqueFile(aTemplateFile) { + // Work on a clone of the provided template file object. + var curFile = aTemplateFile.clone().QueryInterface(Ci.nsILocalFile); + var [base, ext] = DownloadPaths.splitBaseNameAndExtension(curFile.leafName); + // Try other file names, for example "base(1).txt" or "base(1).tar.gz", + // only if the file name initially set already exists. + for (let i = 1; i < 10000 && curFile.exists(); i++) { + curFile.leafName = base + "(" + i + ")" + ext; + } + // At this point we hand off control to createUnique, which will create the + // file with the name we chose, if it is valid. If not, createUnique will + // attempt to modify it again, for example it will shorten very long names + // that can't be created on some platforms, and for which a normal call to + // nsIFile.create would result in NS_ERROR_FILE_NOT_FOUND. This can result + // very rarely in strange names like "base(9999).tar-1.gz" or "ba-1.gz". + curFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + return curFile; + }, + + /** + * Separates the base name from the extension in a file name, recognizing some + * double extensions like ".tar.gz". + * + * @param aLeafName + * The full leaf name to be parsed. Be careful when processing names + * containing leading or trailing dots or spaces. + * @returns [base, ext] + * The base name of the file, which can be empty, and its extension, + * which always includes the leading dot unless it's an empty string. + * Concatenating the two items always results in the original name. + */ + splitBaseNameAndExtension: function DP_splitBaseNameAndExtension(aLeafName) { + // The following regular expression is built from these key parts: + // .*? Matches the base name non-greedily. + // \.[A-Z0-9]{1,3} Up to three letters or numbers preceding a + // double extension. + // \.(?:gz|bz2|Z) The second part of common double extensions. + // \.[^.]* Matches any extension or a single trailing dot. + var [, base, ext] = /(.*?)(\.[A-Z0-9]{1,3}\.(?:gz|bz2|Z)|\.[^.]*)?$/i + .exec(aLeafName); + // Return an empty string instead of undefined if no extension is found. + return [base, ext || ""]; + } +}; diff --git a/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm b/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm new file mode 100644 index 000000000..bccbeda56 --- /dev/null +++ b/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm @@ -0,0 +1,399 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 sts=2 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +this.EXPORTED_SYMBOLS = [ + "DownloadTaskbarProgress", +]; + +// Constants + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +const kTaskbarIDWin = "@mozilla.org/windows-taskbar;1"; +const kTaskbarIDMac = "@mozilla.org/widget/macdocksupport;1"; + +// DownloadTaskbarProgress Object + +this.DownloadTaskbarProgress = +{ + init: function DTP_init() + { + if (DownloadTaskbarProgressUpdater) { + DownloadTaskbarProgressUpdater._init(); + } + }, + + /** + * Called when a browser window appears. This has an effect only when we + * don't already have an active window. + * + * @param aWindow + * The browser window that we'll potentially use to display the + * progress. + */ + onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow) + { + this.init(); + if (!DownloadTaskbarProgressUpdater) { + return; + } + if (!DownloadTaskbarProgressUpdater._activeTaskbarProgress) { + DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, false); + } + }, + + /** + * Called when the download window appears. The download window will take + * over as the active window. + */ + onDownloadWindowLoad: function DTP_onDownloadWindowLoad(aWindow) + { + if (!DownloadTaskbarProgressUpdater) { + return; + } + DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, true); + }, + + /** + * Getters for internal DownloadTaskbarProgressUpdater values + */ + + get activeTaskbarProgress() { + if (!DownloadTaskbarProgressUpdater) { + return null; + } + return DownloadTaskbarProgressUpdater._activeTaskbarProgress; + }, + + get activeWindowIsDownloadWindow() { + if (!DownloadTaskbarProgressUpdater) { + return null; + } + return DownloadTaskbarProgressUpdater._activeWindowIsDownloadWindow; + }, + + get taskbarState() { + if (!DownloadTaskbarProgressUpdater) { + return null; + } + return DownloadTaskbarProgressUpdater._taskbarState; + }, + +}; + +// DownloadTaskbarProgressUpdater Object + +var DownloadTaskbarProgressUpdater = +{ + // / Whether the taskbar is initialized. + _initialized: false, + + // / Reference to the taskbar. + _taskbar: null, + + // / Reference to the download manager. + _dm: null, + + /** + * Initialize and register ourselves as a download progress listener. + */ + _init: function DTPU_init() + { + if (this._initialized) { + return; // Already initialized + } + this._initialized = true; + + if (kTaskbarIDWin in Cc) { + this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar); + if (!this._taskbar.available) { + // The Windows version is probably too old + DownloadTaskbarProgressUpdater = null; + return; + } + } else if (kTaskbarIDMac in Cc) { + this._activeTaskbarProgress = Cc[kTaskbarIDMac]. + getService(Ci.nsITaskbarProgress); + } else { + DownloadTaskbarProgressUpdater = null; + return; + } + + this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; + + this._dm = Cc["@mozilla.org/download-manager;1"]. + getService(Ci.nsIDownloadManager); + this._dm.addPrivacyAwareListener(this); + + this._os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + this._os.addObserver(this, "quit-application-granted", false); + + this._updateStatus(); + // onBrowserWindowLoad/onDownloadWindowLoad are going to set the active + // window, so don't do it here. + }, + + /** + * Unregisters ourselves as a download progress listener. + */ + _uninit: function DTPU_uninit() { + this._dm.removeListener(this); + this._os.removeObserver(this, "quit-application-granted"); + this._activeTaskbarProgress = null; + this._initialized = false; + }, + + /** + * This holds a reference to the taskbar progress for the window we're + * working with. This window would preferably be download window, but can be + * another window if it isn't open. + */ + _activeTaskbarProgress: null, + + // / Whether the active window is the download window + _activeWindowIsDownloadWindow: false, + + /** + * Sets the active window, and whether it's the download window. This takes + * care of clearing out the previous active window's taskbar item, updating + * the taskbar, and setting an onunload listener. + * + * @param aWindow + * The window to set as active. + * @param aIsDownloadWindow + * Whether this window is a download window. + */ + _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow) + { + if (AppConstants.platform == "win") { + // Clear out the taskbar for the old active window. (If there was no active + // window, this is a no-op.) + this._clearTaskbar(); + + this._activeWindowIsDownloadWindow = aIsDownloadWindow; + if (aWindow) { + // Get the taskbar progress for this window + let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIWebNavigation). + QueryInterface(Ci.nsIDocShellTreeItem).treeOwner. + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIXULWindow).docShell; + let taskbarProgress = this._taskbar.getTaskbarProgress(docShell); + this._activeTaskbarProgress = taskbarProgress; + + this._updateTaskbar(); + // _onActiveWindowUnload is idempotent, so we don't need to check whether + // we've already set this before or not. + aWindow.addEventListener("unload", function () { + DownloadTaskbarProgressUpdater._onActiveWindowUnload(taskbarProgress); + }, false); + } + else { + this._activeTaskbarProgress = null; + } + } + }, + + // / Current state displayed on the active window's taskbar item + _taskbarState: null, + _totalSize: 0, + _totalTransferred: 0, + + _shouldSetState: function DTPU_shouldSetState() + { + if (AppConstants.platform == "win") { + // If the active window is not the download manager window, set the state + // only if it is normal or indeterminate. + return this._activeWindowIsDownloadWindow || + (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL || + this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE); + } + return true; + }, + + /** + * Update the active window's taskbar indicator with the current state. There + * are two cases here: + * 1. If the active window is the download window, then we always update + * the taskbar indicator. + * 2. If the active window isn't the download window, then we update only if + * the status is normal or indeterminate. i.e. one or more downloads are + * currently progressing or in scan mode. If we aren't, then we clear the + * indicator. + */ + _updateTaskbar: function DTPU_updateTaskbar() + { + if (!this._activeTaskbarProgress) { + return; + } + + if (this._shouldSetState()) { + this._activeTaskbarProgress.setProgressState(this._taskbarState, + this._totalTransferred, + this._totalSize); + } + // Clear any state otherwise + else { + this._clearTaskbar(); + } + }, + + /** + * Clear taskbar state. This is needed: + * - to transfer the indicator off a window before transferring it onto + * another one + * - whenever we don't want to show it for a non-download window. + */ + _clearTaskbar: function DTPU_clearTaskbar() + { + if (this._activeTaskbarProgress) { + this._activeTaskbarProgress.setProgressState( + Ci.nsITaskbarProgress.STATE_NO_PROGRESS + ); + } + }, + + /** + * Update this._taskbarState, this._totalSize and this._totalTransferred. + * This is called when the download manager is initialized or when the + * progress or state of a download changes. + * We compute the number of active and paused downloads, and the total size + * and total amount already transferred across whichever downloads we have + * the data for. + * - If there are no active downloads, then we don't want to show any + * progress. + * - If the number of active downloads is equal to the number of paused + * downloads, then we show a paused indicator if we know the size of at + * least one download, and no indicator if we don't. + * - If the number of active downloads is more than the number of paused + * downloads, then we show a "normal" indicator if we know the size of at + * least one download, and an indeterminate indicator if we don't. + */ + _updateStatus: function DTPU_updateStatus() + { + let numActive = this._dm.activeDownloadCount + this._dm.activePrivateDownloadCount; + let totalSize = 0, totalTransferred = 0; + + if (numActive == 0) { + this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; + } + else { + let numPaused = 0, numScanning = 0; + + // Enumerate all active downloads + [this._dm.activeDownloads, this._dm.activePrivateDownloads].forEach(function(downloads) { + while (downloads.hasMoreElements()) { + let download = downloads.getNext().QueryInterface(Ci.nsIDownload); + // Only set values if we actually know the download size + if (download.percentComplete != -1) { + totalSize += download.size; + totalTransferred += download.amountTransferred; + } + // We might need to display a paused state, so track this + if (download.state == this._dm.DOWNLOAD_PAUSED) { + numPaused++; + } else if (download.state == this._dm.DOWNLOAD_SCANNING) { + numScanning++; + } + } + }.bind(this)); + + // If all downloads are paused, show the progress as paused, unless we + // don't have any information about sizes, in which case we don't + // display anything + if (numActive == numPaused) { + if (totalSize == 0) { + this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS; + totalTransferred = 0; + } + else { + this._taskbarState = Ci.nsITaskbarProgress.STATE_PAUSED; + } + } + // If at least one download is not paused, and we don't have any + // information about download sizes, display an indeterminate indicator + else if (totalSize == 0 || numActive == numScanning) { + this._taskbarState = Ci.nsITaskbarProgress.STATE_INDETERMINATE; + totalSize = 0; + totalTransferred = 0; + } + // Otherwise display a normal progress bar + else { + this._taskbarState = Ci.nsITaskbarProgress.STATE_NORMAL; + } + } + + this._totalSize = totalSize; + this._totalTransferred = totalTransferred; + }, + + /** + * Called when a window that at one point has been an active window is + * closed. If this window is currently the active window, we need to look for + * another window and make that our active window. + * + * This function is idempotent, so multiple calls for the same window are not + * a problem. + * + * @param aTaskbarProgress + * The taskbar progress for the window that is being unloaded. + */ + _onActiveWindowUnload: function DTPU_onActiveWindowUnload(aTaskbarProgress) + { + if (this._activeTaskbarProgress == aTaskbarProgress) { + let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator); + let windows = windowMediator.getEnumerator(null); + let newActiveWindow = null; + if (windows.hasMoreElements()) { + newActiveWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow); + } + + // We aren't ever going to reach this point while the download manager is + // open, so it's safe to assume false for the second operand + this._setActiveWindow(newActiveWindow, false); + } + }, + + // nsIDownloadProgressListener + + /** + * Update status if a download's progress has changed. + */ + onProgressChange: function DTPU_onProgressChange() + { + this._updateStatus(); + this._updateTaskbar(); + }, + + /** + * Update status if a download's state has changed. + */ + onDownloadStateChange: function DTPU_onDownloadStateChange() + { + this._updateStatus(); + this._updateTaskbar(); + }, + + onSecurityChange: function() { }, + + onStateChange: function() { }, + + observe: function DTPU_observe(aSubject, aTopic, aData) { + if (aTopic == "quit-application-granted") { + this._uninit(); + } + } +}; diff --git a/toolkit/mozapps/downloads/DownloadUtils.jsm b/toolkit/mozapps/downloads/DownloadUtils.jsm new file mode 100644 index 000000000..3ebdd605e --- /dev/null +++ b/toolkit/mozapps/downloads/DownloadUtils.jsm @@ -0,0 +1,600 @@ +/* vim: sw=2 ts=2 sts=2 expandtab filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = [ "DownloadUtils" ]; + +/** + * This module provides the DownloadUtils object which contains useful methods + * for downloads such as displaying file sizes, transfer times, and download + * locations. + * + * List of methods: + * + * [string status, double newLast] + * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes, + * [optional] double aSpeed, [optional] double aLastSec) + * + * string progress + * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes) + * + * [string timeLeft, double newLast] + * getTimeLeft(double aSeconds, [optional] double aLastSec) + * + * [string dateCompact, string dateComplete] + * getReadableDates(Date aDate, [optional] Date aNow) + * + * [string displayHost, string fullHost] + * getURIHost(string aURIString) + * + * [string convertedBytes, string units] + * convertByteUnits(int aBytes) + * + * [int time, string units, int subTime, string subUnits] + * convertTimeUnits(double aSecs) + */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + +this.__defineGetter__("gDecimalSymbol", function() { + delete this.gDecimalSymbol; + return this.gDecimalSymbol = Number(5.4).toLocaleString().match(/\D/); +}); + +var localeNumberFormatCache = new Map(); +function getLocaleNumberFormat(fractionDigits) { + // Backward compatibility: don't use localized digits + let locale = Intl.NumberFormat().resolvedOptions().locale + + "-u-nu-latn"; + let key = locale + "_" + fractionDigits; + if (!localeNumberFormatCache.has(key)) { + localeNumberFormatCache.set(key, + Intl.NumberFormat(locale, + { maximumFractionDigits: fractionDigits, + minimumFractionDigits: fractionDigits })); + } + return localeNumberFormatCache.get(key); +} + +const kDownloadProperties = + "chrome://mozapps/locale/downloads/downloads.properties"; + +var gStr = { + statusFormat: "statusFormat3", + statusFormatInfiniteRate: "statusFormatInfiniteRate", + statusFormatNoRate: "statusFormatNoRate", + transferSameUnits: "transferSameUnits2", + transferDiffUnits: "transferDiffUnits2", + transferNoTotal: "transferNoTotal2", + timePair: "timePair2", + timeLeftSingle: "timeLeftSingle2", + timeLeftDouble: "timeLeftDouble2", + timeFewSeconds: "timeFewSeconds", + timeUnknown: "timeUnknown", + monthDate: "monthDate2", + yesterday: "yesterday", + doneScheme: "doneScheme2", + doneFileScheme: "doneFileScheme", + units: ["bytes", "kilobyte", "megabyte", "gigabyte"], + // Update timeSize in convertTimeUnits if changing the length of this array + timeUnits: ["seconds", "minutes", "hours", "days"], + infiniteRate: "infiniteRate", +}; + +// This lazily initializes the string bundle upon first use. +this.__defineGetter__("gBundle", function() { + delete this.gBundle; + return this.gBundle = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService). + createBundle(kDownloadProperties); +}); + +// Keep track of at most this many second/lastSec pairs so that multiple calls +// to getTimeLeft produce the same time left +const kCachedLastMaxSize = 10; +var gCachedLast = []; + +this.DownloadUtils = { + /** + * Generate a full status string for a download given its current progress, + * total size, speed, last time remaining + * + * @param aCurrBytes + * Number of bytes transferred so far + * @param [optional] aMaxBytes + * Total number of bytes or -1 for unknown + * @param [optional] aSpeed + * Current transfer rate in bytes/sec or -1 for unknown + * @param [optional] aLastSec + * Last time remaining in seconds or Infinity for unknown + * @return A pair: [download status text, new value of "last seconds"] + */ + getDownloadStatus: function DU_getDownloadStatus(aCurrBytes, aMaxBytes, + aSpeed, aLastSec) + { + let [transfer, timeLeft, newLast, normalizedSpeed] + = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec); + + let [rate, unit] = DownloadUtils.convertByteUnits(normalizedSpeed); + + let status; + if (rate === "Infinity") { + // Infinity download speed doesn't make sense. Show a localized phrase instead. + let params = [transfer, gBundle.GetStringFromName(gStr.infiniteRate), timeLeft]; + status = gBundle.formatStringFromName(gStr.statusFormatInfiniteRate, params, + params.length); + } + else { + let params = [transfer, rate, unit, timeLeft]; + status = gBundle.formatStringFromName(gStr.statusFormat, params, + params.length); + } + return [status, newLast]; + }, + + /** + * Generate a status string for a download given its current progress, + * total size, speed, last time remaining. The status string contains the + * time remaining, as well as the total bytes downloaded. Unlike + * getDownloadStatus, it does not include the rate of download. + * + * @param aCurrBytes + * Number of bytes transferred so far + * @param [optional] aMaxBytes + * Total number of bytes or -1 for unknown + * @param [optional] aSpeed + * Current transfer rate in bytes/sec or -1 for unknown + * @param [optional] aLastSec + * Last time remaining in seconds or Infinity for unknown + * @return A pair: [download status text, new value of "last seconds"] + */ + getDownloadStatusNoRate: + function DU_getDownloadStatusNoRate(aCurrBytes, aMaxBytes, aSpeed, + aLastSec) + { + let [transfer, timeLeft, newLast] + = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec); + + let params = [transfer, timeLeft]; + let status = gBundle.formatStringFromName(gStr.statusFormatNoRate, params, + params.length); + return [status, newLast]; + }, + + /** + * Helper function that returns a transfer string, a time remaining string, + * and a new value of "last seconds". + * @param aCurrBytes + * Number of bytes transferred so far + * @param [optional] aMaxBytes + * Total number of bytes or -1 for unknown + * @param [optional] aSpeed + * Current transfer rate in bytes/sec or -1 for unknown + * @param [optional] aLastSec + * Last time remaining in seconds or Infinity for unknown + * @return A triple: [amount transferred string, time remaining string, + * new value of "last seconds"] + */ + _deriveTransferRate: function DU__deriveTransferRate(aCurrBytes, + aMaxBytes, aSpeed, + aLastSec) + { + if (aMaxBytes == null) + aMaxBytes = -1; + if (aSpeed == null) + aSpeed = -1; + if (aLastSec == null) + aLastSec = Infinity; + + // Calculate the time remaining if we have valid values + let seconds = (aSpeed > 0) && (aMaxBytes > 0) ? + (aMaxBytes - aCurrBytes) / aSpeed : -1; + + let transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes); + let [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec); + return [transfer, timeLeft, newLast, aSpeed]; + }, + + /** + * Generate the transfer progress string to show the current and total byte + * size. Byte units will be as large as possible and the same units for + * current and max will be suppressed for the former. + * + * @param aCurrBytes + * Number of bytes transferred so far + * @param [optional] aMaxBytes + * Total number of bytes or -1 for unknown + * @return The transfer progress text + */ + getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes) + { + if (aMaxBytes == null) + aMaxBytes = -1; + + let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes); + let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes); + + // Figure out which byte progress string to display + let name, values; + if (aMaxBytes < 0) { + name = gStr.transferNoTotal; + values = [ + progress, + progressUnits, + ]; + } else if (progressUnits == totalUnits) { + name = gStr.transferSameUnits; + values = [ + progress, + total, + totalUnits, + ]; + } else { + name = gStr.transferDiffUnits; + values = [ + progress, + progressUnits, + total, + totalUnits, + ]; + } + + return gBundle.formatStringFromName(name, values, values.length); + }, + + /** + * Generate a "time left" string given an estimate on the time left and the + * last time. The extra time is used to give a better estimate on the time to + * show. Both the time values are doubles instead of integers to help get + * sub-second accuracy for current and future estimates. + * + * @param aSeconds + * Current estimate on number of seconds left for the download + * @param [optional] aLastSec + * Last time remaining in seconds or Infinity for unknown + * @return A pair: [time left text, new value of "last seconds"] + */ + getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec) + { + if (aLastSec == null) + aLastSec = Infinity; + + if (aSeconds < 0) + return [gBundle.GetStringFromName(gStr.timeUnknown), aLastSec]; + + // Try to find a cached lastSec for the given second + aLastSec = gCachedLast.reduce((aResult, aItem) => + aItem[0] == aSeconds ? aItem[1] : aResult, aLastSec); + + // Add the current second/lastSec pair unless we have too many + gCachedLast.push([aSeconds, aLastSec]); + if (gCachedLast.length > kCachedLastMaxSize) + gCachedLast.shift(); + + // Apply smoothing only if the new time isn't a huge change -- e.g., if the + // new time is more than half the previous time; this is useful for + // downloads that start/resume slowly + if (aSeconds > aLastSec / 2) { + // Apply hysteresis to favor downward over upward swings + // 30% of down and 10% of up (exponential smoothing) + let diff = aSeconds - aLastSec; + aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff; + + // If the new time is similar, reuse something close to the last seconds, + // but subtract a little to provide forward progress + let diffPct = diff / aLastSec * 100; + if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5) + aSeconds = aLastSec - (diff < 0 ? .4 : .2); + } + + // Decide what text to show for the time + let timeLeft; + if (aSeconds < 4) { + // Be friendly in the last few seconds + timeLeft = gBundle.GetStringFromName(gStr.timeFewSeconds); + } else { + // Convert the seconds into its two largest units to display + let [time1, unit1, time2, unit2] = + DownloadUtils.convertTimeUnits(aSeconds); + + let pair1 = + gBundle.formatStringFromName(gStr.timePair, [time1, unit1], 2); + let pair2 = + gBundle.formatStringFromName(gStr.timePair, [time2, unit2], 2); + + // Only show minutes for under 1 hour unless there's a few minutes left; + // or the second pair is 0. + if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) { + timeLeft = gBundle.formatStringFromName(gStr.timeLeftSingle, + [pair1], 1); + } else { + // We've got 2 pairs of times to display + timeLeft = gBundle.formatStringFromName(gStr.timeLeftDouble, + [pair1, pair2], 2); + } + } + + return [timeLeft, aSeconds]; + }, + + /** + * Converts a Date object to two readable formats, one compact, one complete. + * The compact format is relative to the current date, and is not an accurate + * representation. For example, only the time is displayed for today. The + * complete format always includes both the date and the time, excluding the + * seconds, and is often shown when hovering the cursor over the compact + * representation. + * + * @param aDate + * Date object representing the date and time to format. It is assumed + * that this value represents a past date. + * @param [optional] aNow + * Date object representing the current date and time. The real date + * and time of invocation is used if this parameter is omitted. + * @return A pair: [compact text, complete text] + */ + getReadableDates: function DU_getReadableDates(aDate, aNow) + { + if (!aNow) { + aNow = new Date(); + } + + let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"] + .getService(Ci.nsIScriptableDateFormat); + + // Figure out when today begins + let today = new Date(aNow.getFullYear(), aNow.getMonth(), aNow.getDate()); + + // Get locale to use for date/time formatting + // TODO: Remove Intl fallback when bug 1215247 is fixed. + const locale = typeof Intl === "undefined" + ? undefined + : Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIXULChromeRegistry) + .getSelectedLocale("global", true); + + // Figure out if the time is from today, yesterday, this week, etc. + let dateTimeCompact; + if (aDate >= today) { + // After today started, show the time + dateTimeCompact = dts.FormatTime("", + dts.timeFormatNoSeconds, + aDate.getHours(), + aDate.getMinutes(), + 0); + } else if (today - aDate < (24 * 60 * 60 * 1000)) { + // After yesterday started, show yesterday + dateTimeCompact = gBundle.GetStringFromName(gStr.yesterday); + } else if (today - aDate < (6 * 24 * 60 * 60 * 1000)) { + // After last week started, show day of week + dateTimeCompact = typeof Intl === "undefined" + ? aDate.toLocaleFormat("%A") + : aDate.toLocaleDateString(locale, { weekday: "long" }); + } else { + // Show month/day + let month = typeof Intl === "undefined" + ? aDate.toLocaleFormat("%B") + : aDate.toLocaleDateString(locale, { month: "long" }); + let date = aDate.getDate(); + dateTimeCompact = gBundle.formatStringFromName(gStr.monthDate, [month, date], 2); + } + + let dateTimeFull = dts.FormatDateTime("", + dts.dateFormatLong, + dts.timeFormatNoSeconds, + aDate.getFullYear(), + aDate.getMonth() + 1, + aDate.getDate(), + aDate.getHours(), + aDate.getMinutes(), + 0); + + return [dateTimeCompact, dateTimeFull]; + }, + + /** + * Get the appropriate display host string for a URI string depending on if + * the URI has an eTLD + 1, is an IP address, a local file, or other protocol + * + * @param aURIString + * The URI string to try getting an eTLD + 1, etc. + * @return A pair: [display host for the URI string, full host name] + */ + getURIHost: function DU_getURIHost(aURIString) + { + let ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]. + getService(Ci.nsIEffectiveTLDService); + let idnService = Cc["@mozilla.org/network/idn-service;1"]. + getService(Ci.nsIIDNService); + + // Get a URI that knows about its components + let uri; + try { + uri = ioService.newURI(aURIString, null, null); + } catch (ex) { + return ["", ""]; + } + + // Get the inner-most uri for schemes like jar: + if (uri instanceof Ci.nsINestedURI) + uri = uri.innermostURI; + + let fullHost; + try { + // Get the full host name; some special URIs fail (data: jar:) + fullHost = uri.host; + } catch (e) { + fullHost = ""; + } + + let displayHost; + try { + // This might fail if it's an IP address or doesn't have more than 1 part + let baseDomain = eTLDService.getBaseDomain(uri); + + // Convert base domain for display; ignore the isAscii out param + displayHost = idnService.convertToDisplayIDN(baseDomain, {}); + } catch (e) { + // Default to the host name + displayHost = fullHost; + } + + // Check if we need to show something else for the host + if (uri.scheme == "file") { + // Display special text for file protocol + displayHost = gBundle.GetStringFromName(gStr.doneFileScheme); + fullHost = displayHost; + } else if (displayHost.length == 0) { + // Got nothing; show the scheme (data: about: moz-icon:) + displayHost = + gBundle.formatStringFromName(gStr.doneScheme, [uri.scheme], 1); + fullHost = displayHost; + } else if (uri.port != -1) { + // Tack on the port if it's not the default port + let port = ":" + uri.port; + displayHost += port; + fullHost += port; + } + + return [displayHost, fullHost]; + }, + + /** + * Converts a number of bytes to the appropriate unit that results in an + * internationalized number that needs fewer than 4 digits. + * + * @param aBytes + * Number of bytes to convert + * @return A pair: [new value with 3 sig. figs., its unit] + */ + convertByteUnits: function DU_convertByteUnits(aBytes) + { + let unitIndex = 0; + + // Convert to next unit if it needs 4 digits (after rounding), but only if + // we know the name of the next unit + while ((aBytes >= 999.5) && (unitIndex < gStr.units.length - 1)) { + aBytes /= 1024; + unitIndex++; + } + + // Get rid of insignificant bits by truncating to 1 or 0 decimal points + // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235 + // added in bug 462064: (unitIndex != 0) makes sure that no decimal digit for bytes appears when aBytes < 100 + let fractionDigits = (aBytes > 0) && (aBytes < 100) && (unitIndex != 0) ? 1 : 0; + + // Don't try to format Infinity values using NumberFormat. + if (aBytes === Infinity) { + aBytes = "Infinity"; + } else if (typeof Intl != "undefined") { + aBytes = getLocaleNumberFormat(fractionDigits) + .format(aBytes); + } else { + // FIXME: Fall back to the old hack, will be fixed in bug 1200494. + aBytes = aBytes.toFixed(fractionDigits); + if (gDecimalSymbol != ".") { + aBytes = aBytes.replace(".", gDecimalSymbol); + } + } + + return [aBytes, gBundle.GetStringFromName(gStr.units[unitIndex])]; + }, + + /** + * Converts a number of seconds to the two largest units. Time values are + * whole numbers, and units have the correct plural/singular form. + * + * @param aSecs + * Seconds to convert into the appropriate 2 units + * @return 4-item array [first value, its unit, second value, its unit] + */ + convertTimeUnits: function DU_convertTimeUnits(aSecs) + { + // These are the maximum values for seconds, minutes, hours corresponding + // with gStr.timeUnits without the last item + let timeSize = [60, 60, 24]; + + let time = aSecs; + let scale = 1; + let unitIndex = 0; + + // Keep converting to the next unit while we have units left and the + // current one isn't the largest unit possible + while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) { + time /= timeSize[unitIndex]; + scale *= timeSize[unitIndex]; + unitIndex++; + } + + let value = convertTimeUnitsValue(time); + let units = convertTimeUnitsUnits(value, unitIndex); + + let extra = aSecs - value * scale; + let nextIndex = unitIndex - 1; + + // Convert the extra time to the next largest unit + for (let index = 0; index < nextIndex; index++) + extra /= timeSize[index]; + + let value2 = convertTimeUnitsValue(extra); + let units2 = convertTimeUnitsUnits(value2, nextIndex); + + return [value, units, value2, units2]; + }, +}; + +/** + * Private helper for convertTimeUnits that gets the display value of a time + * + * @param aTime + * Time value for display + * @return An integer value for the time rounded down + */ +function convertTimeUnitsValue(aTime) +{ + return Math.floor(aTime); +} + +/** + * Private helper for convertTimeUnits that gets the display units of a time + * + * @param aTime + * Time value for display + * @param aIndex + * Index into gStr.timeUnits for the appropriate unit + * @return The appropriate plural form of the unit for the time + */ +function convertTimeUnitsUnits(aTime, aIndex) +{ + // Negative index would be an invalid unit, so just give empty + if (aIndex < 0) + return ""; + + return PluralForm.get(aTime, gBundle.GetStringFromName(gStr.timeUnits[aIndex])); +} + +/** + * Private helper function to log errors to the error console and command line + * + * @param aMsg + * Error message to log or an array of strings to concat + */ +function log(aMsg) +{ + let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg); + Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService). + logStringMessage(msg); + dump(msg + "\n"); +} diff --git a/toolkit/mozapps/downloads/content/DownloadProgressListener.js b/toolkit/mozapps/downloads/content/DownloadProgressListener.js new file mode 100644 index 000000000..ab349baf2 --- /dev/null +++ b/toolkit/mozapps/downloads/content/DownloadProgressListener.js @@ -0,0 +1,117 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * DownloadProgressListener "class" is used to help update download items shown + * in the Download Manager UI such as displaying amount transferred, transfer + * rate, and time left for each download. + * + * This class implements the nsIDownloadProgressListener interface. + */ +function DownloadProgressListener() {} + +DownloadProgressListener.prototype = { + // nsISupports + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener]), + + // nsIDownloadProgressListener + + onDownloadStateChange: function dlPL_onDownloadStateChange(aState, aDownload) + { + // Update window title in-case we don't get all progress notifications + onUpdateProgress(); + + let dl; + let state = aDownload.state; + switch (state) { + case nsIDM.DOWNLOAD_QUEUED: + prependList(aDownload); + break; + + case nsIDM.DOWNLOAD_BLOCKED_POLICY: + prependList(aDownload); + // Should fall through, this is a final state but DOWNLOAD_QUEUED + // is skipped. See nsDownloadManager::AddDownload. + case nsIDM.DOWNLOAD_FAILED: + case nsIDM.DOWNLOAD_CANCELED: + case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: + case nsIDM.DOWNLOAD_DIRTY: + case nsIDM.DOWNLOAD_FINISHED: + downloadCompleted(aDownload); + if (state == nsIDM.DOWNLOAD_FINISHED) + autoRemoveAndClose(aDownload); + break; + case nsIDM.DOWNLOAD_DOWNLOADING: { + dl = getDownload(aDownload.id); + + // At this point, we know if we are an indeterminate download or not + dl.setAttribute("progressmode", aDownload.percentComplete == -1 ? + "undetermined" : "normal"); + + // As well as knowing the referrer + let referrer = aDownload.referrer; + if (referrer) + dl.setAttribute("referrer", referrer.spec); + + break; + } + } + + // autoRemoveAndClose could have already closed our window... + try { + if (!dl) + dl = getDownload(aDownload.id); + + // Update to the new state + dl.setAttribute("state", state); + + // Update ui text values after switching states + updateTime(dl); + updateStatus(dl); + updateButtons(dl); + } catch (e) { } + }, + + onProgressChange: function dlPL_onProgressChange(aWebProgress, aRequest, + aCurSelfProgress, + aMaxSelfProgress, + aCurTotalProgress, + aMaxTotalProgress, aDownload) + { + var download = getDownload(aDownload.id); + + // Update this download's progressmeter + if (aDownload.percentComplete != -1) { + download.setAttribute("progress", aDownload.percentComplete); + + // Dispatch ValueChange for a11y + let event = document.createEvent("Events"); + event.initEvent("ValueChange", true, true); + document.getAnonymousElementByAttribute(download, "anonid", "progressmeter") + .dispatchEvent(event); + } + + // Update the progress so the status can be correctly updated + download.setAttribute("currBytes", aDownload.amountTransferred); + download.setAttribute("maxBytes", aDownload.size); + + // Update the rest of the UI (bytes transferred, bytes total, download rate, + // time remaining). + updateStatus(download, aDownload); + + // Update window title + onUpdateProgress(); + }, + + onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) + { + }, + + onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) + { + } +}; diff --git a/toolkit/mozapps/downloads/content/download.xml b/toolkit/mozapps/downloads/content/download.xml new file mode 100644 index 000000000..1d4b87270 --- /dev/null +++ b/toolkit/mozapps/downloads/content/download.xml @@ -0,0 +1,327 @@ + + + + + + %downloadDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/downloads/content/downloads.css b/toolkit/mozapps/downloads/content/downloads.css new file mode 100644 index 000000000..dcb648d62 --- /dev/null +++ b/toolkit/mozapps/downloads/content/downloads.css @@ -0,0 +1,50 @@ +/* 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/. */ + +richlistitem[type="download"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-starting'); + -moz-box-orient: vertical; +} + +richlistitem[type="download"][state="0"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-downloading'); +} + +richlistitem[type="download"][state="1"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-done'); +} + +richlistitem[type="download"][state="2"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-failed'); +} + +richlistitem[type="download"][state="3"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-canceled'); +} + +richlistitem[type="download"][state="4"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-paused'); +} + +richlistitem[type="download"][state="6"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-parental'); +} + +richlistitem[type="download"][state="7"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-scanning'); +} + +richlistitem[type="download"][state="8"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-dirty'); +} + +richlistitem[type="download"][state="9"] { + -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-policy'); +} + +/* Only focus buttons in the selected item*/ +richlistitem[type="download"]:not([selected="true"]) button { + -moz-user-focus: none; +} + diff --git a/toolkit/mozapps/downloads/content/downloads.js b/toolkit/mozapps/downloads/content/downloads.js new file mode 100644 index 000000000..92a9f7593 --- /dev/null +++ b/toolkit/mozapps/downloads/content/downloads.js @@ -0,0 +1,1320 @@ +/* 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"; + +// Globals + +const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone"; +const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen"; +const PREF_BDM_SCANWHENDONE = "browser.download.manager.scanWhenDone"; + +const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", + "nsILocalFile", "initWithPath"); + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/DownloadUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/AppConstants.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + +const nsIDM = Ci.nsIDownloadManager; + +var gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM); +var gDownloadManagerUI = Cc["@mozilla.org/download-manager-ui;1"]. + getService(Ci.nsIDownloadManagerUI); + +var gDownloadListener = null; +var gDownloadsView = null; +var gSearchBox = null; +var gSearchTerms = []; +var gBuilder = 0; + +// This variable is used when performing commands on download items and gives +// the command the ability to do something after all items have been operated +// on. The following convention is used to handle the value of the variable: +// whenever we aren't performing a command, the value is |undefined|; just +// before executing commands, the value will be set to |null|; and when +// commands want to create a callback, they set the value to be a callback +// function to be executed after all download items have been visited. +var gPerformAllCallback; + +// Control the performance of the incremental list building by setting how many +// milliseconds to wait before building more of the list and how many items to +// add between each delay. +const gListBuildDelay = 300; +const gListBuildChunk = 3; + +// Array of download richlistitem attributes to check when searching +const gSearchAttributes = [ + "target", + "status", + "dateTime", +]; + +// If the user has interacted with the window in a significant way, we should +// not auto-close the window. Tough UI decisions about what is "significant." +var gUserInteracted = false; + +// These strings will be converted to the corresponding ones from the string +// bundle on startup. +var gStr = { + paused: "paused", + cannotPause: "cannotPause", + doneStatus: "doneStatus", + doneSize: "doneSize", + doneSizeUnknown: "doneSizeUnknown", + stateFailed: "stateFailed", + stateCanceled: "stateCanceled", + stateBlockedParentalControls: "stateBlocked", + stateBlockedPolicy: "stateBlockedPolicy", + stateDirty: "stateDirty", + downloadsTitleFiles: "downloadsTitleFiles", + downloadsTitlePercent: "downloadsTitlePercent", + fileExecutableSecurityWarningTitle: "fileExecutableSecurityWarningTitle", + fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk" +}; + +// The statement to query for downloads that are active or match the search +var gStmt = null; + +// Start/Stop Observers + +function downloadCompleted(aDownload) +{ + // The download is changing state, so update the clear list button + updateClearListButton(); + + // Wrap this in try...catch since this can be called while shutting down... + // it doesn't really matter if it fails then since well.. we're shutting down + // and there's no UI to update! + try { + let dl = getDownload(aDownload.id); + + // Update attributes now that we've finished + dl.setAttribute("startTime", Math.round(aDownload.startTime / 1000)); + dl.setAttribute("endTime", Date.now()); + dl.setAttribute("currBytes", aDownload.amountTransferred); + dl.setAttribute("maxBytes", aDownload.size); + + // Move the download below active if it should stay in the list + if (downloadMatchesSearch(dl)) { + // Iterate down until we find a non-active download + let next = dl.nextSibling; + while (next && next.inProgress) + next = next.nextSibling; + + // Move the item + gDownloadsView.insertBefore(dl, next); + } else { + removeFromView(dl); + } + + // getTypeFromFile fails if it can't find a type for this file. + try { + // Refresh the icon, so that executable icons are shown. + var mimeService = Cc["@mozilla.org/mime;1"]. + getService(Ci.nsIMIMEService); + var contentType = mimeService.getTypeFromFile(aDownload.targetFile); + + var listItem = getDownload(aDownload.id) + var oldImage = listItem.getAttribute("image"); + // Tacking on contentType bypasses cache + listItem.setAttribute("image", oldImage + "&contentType=" + contentType); + } catch (e) { } + + if (gDownloadManager.activeDownloadCount == 0) + document.title = document.documentElement.getAttribute("statictitle"); + + gDownloadManagerUI.getAttention(); + } + catch (e) { } +} + +function autoRemoveAndClose(aDownload) +{ + var pref = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + + if (gDownloadManager.activeDownloadCount == 0) { + // For the moment, just use the simple heuristic that if this window was + // opened by the download process, rather than by the user, it should + // auto-close if the pref is set that way. If the user opened it themselves, + // it should not close until they explicitly close it. Additionally, the + // preference to control the feature may not be set, so defaulting to + // keeping the window open. + let autoClose = false; + try { + autoClose = pref.getBoolPref(PREF_BDM_CLOSEWHENDONE); + } catch (e) { } + var autoOpened = + !window.opener || window.opener.location.href == window.location.href; + if (autoClose && autoOpened && !gUserInteracted) { + gCloseDownloadManager(); + return true; + } + } + + return false; +} + +// This function can be overwritten by extensions that wish to place the +// Download Window in another part of the UI. +function gCloseDownloadManager() +{ + window.close(); +} + +// Download Event Handlers + +function cancelDownload(aDownload) +{ + gDownloadManager.cancelDownload(aDownload.getAttribute("dlid")); + + // XXXben - + // If we got here because we resumed the download, we weren't using a temp file + // because we used saveURL instead. (this is because the proper download mechanism + // employed by the helper app service isn't fully accessible yet... should be fixed... + // talk to bz...) + // the upshot is we have to delete the file if it exists. + var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); + + if (f.exists()) + f.remove(false); +} + +function pauseDownload(aDownload) +{ + var id = aDownload.getAttribute("dlid"); + gDownloadManager.pauseDownload(id); +} + +function resumeDownload(aDownload) +{ + gDownloadManager.resumeDownload(aDownload.getAttribute("dlid")); +} + +function removeDownload(aDownload) +{ + gDownloadManager.removeDownload(aDownload.getAttribute("dlid")); +} + +function retryDownload(aDownload) +{ + removeFromView(aDownload); + gDownloadManager.retryDownload(aDownload.getAttribute("dlid")); +} + +function showDownload(aDownload) +{ + var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); + + try { + // Show the directory containing the file and select the file + f.reveal(); + } catch (e) { + // If reveal fails for some reason (e.g., it's not implemented on unix or + // the file doesn't exist), try using the parent if we have it. + let parent = f.parent.QueryInterface(Ci.nsILocalFile); + if (!parent) + return; + + try { + // "Double click" the parent directory to show where the file should be + parent.launch(); + } catch (e) { + // If launch also fails (probably because it's not implemented), let the + // OS handler try to open the parent + openExternal(parent); + } + } +} + +function onDownloadDblClick(aEvent) +{ + // Only do the default action for double primary clicks + if (aEvent.button == 0 && aEvent.target.selected) + doDefaultForSelected(); +} + +function openDownload(aDownload) +{ + var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); + if (f.isExecutable()) { + var dontAsk = false; + var pref = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + try { + dontAsk = !pref.getBoolPref(PREF_BDM_ALERTONEXEOPEN); + } catch (e) { } + + if (AppConstants.platform == "win") { + // On Vista and above, we rely on native security prompting for + // downloaded content unless it's disabled. + try { + var sysInfo = Cc["@mozilla.org/system-info;1"]. + getService(Ci.nsIPropertyBag2); + if (parseFloat(sysInfo.getProperty("version")) >= 6 && + pref.getBoolPref(PREF_BDM_SCANWHENDONE)) { + dontAsk = true; + } + } catch (ex) { } + } + + if (!dontAsk) { + var strings = document.getElementById("downloadStrings"); + var name = aDownload.getAttribute("target"); + var message = strings.getFormattedString("fileExecutableSecurityWarning", [name, name]); + + let title = gStr.fileExecutableSecurityWarningTitle; + let dontAsk = gStr.fileExecutableSecurityWarningDontAsk; + + var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Ci.nsIPromptService); + var checkbox = { value: false }; + var open = promptSvc.confirmCheck(window, title, message, dontAsk, checkbox); + + if (!open) + return; + pref.setBoolPref(PREF_BDM_ALERTONEXEOPEN, !checkbox.value); + } + } + try { + try { + let download = gDownloadManager.getDownload(aDownload.getAttribute("dlid")); + let mimeInfo = download.MIMEInfo; + if (mimeInfo.preferredAction == mimeInfo.useHelperApp) { + mimeInfo.launchWithFile(f); + return; + } + } catch (ex) { + } + f.launch(); + } catch (ex) { + // if launch fails, try sending it through the system's external + // file: URL handler + openExternal(f); + } +} + +function openReferrer(aDownload) +{ + openURL(getReferrerOrSource(aDownload)); +} + +function copySourceLocation(aDownload) +{ + var uri = aDownload.getAttribute("uri"); + var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. + getService(Ci.nsIClipboardHelper); + + // Check if we should initialize a callback + if (gPerformAllCallback === null) { + let uris = []; + gPerformAllCallback = aURI => aURI ? uris.push(aURI) : + clipboard.copyString(uris.join("\n")); + } + + // We have a callback to use, so use it to add a uri + if (typeof gPerformAllCallback == "function") + gPerformAllCallback(uri); + else { + // It's a plain copy source, so copy it + clipboard.copyString(uri); + } +} + +/** + * Remove the currently shown downloads from the download list. + */ +function clearDownloadList() { + // Clear the whole list if there's no search + if (gSearchTerms == "") { + gDownloadManager.cleanUp(); + return; + } + + // Remove each download starting from the end until we hit a download + // that is in progress + let item; + while ((item = gDownloadsView.lastChild) && !item.inProgress) + removeDownload(item); + + // Clear the input as if the user did it and move focus to the list + gSearchBox.value = ""; + gSearchBox.doCommand(); + gDownloadsView.focus(); +} + +// This is called by the progress listener. +var gLastComputedMean = -1; +var gLastActiveDownloads = 0; +function onUpdateProgress() +{ + let numActiveDownloads = gDownloadManager.activeDownloadCount; + + // Use the default title and reset "last" values if there's no downloads + if (numActiveDownloads == 0) { + document.title = document.documentElement.getAttribute("statictitle"); + gLastComputedMean = -1; + gLastActiveDownloads = 0; + + return; + } + + // Establish the mean transfer speed and amount downloaded. + var mean = 0; + var base = 0; + var dls = gDownloadManager.activeDownloads; + while (dls.hasMoreElements()) { + let dl = dls.getNext(); + if (dl.percentComplete < 100 && dl.size > 0) { + mean += dl.amountTransferred; + base += dl.size; + } + } + + // Calculate the percent transferred, unless we don't have a total file size + let title = gStr.downloadsTitlePercent; + if (base == 0) + title = gStr.downloadsTitleFiles; + else + mean = Math.floor((mean / base) * 100); + + // Update title of window + if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) { + gLastComputedMean = mean; + gLastActiveDownloads = numActiveDownloads; + + // Get the correct plural form and insert number of downloads and percent + title = PluralForm.get(numActiveDownloads, title); + title = replaceInsert(title, 1, numActiveDownloads); + title = replaceInsert(title, 2, mean); + + document.title = title; + } +} + +// Startup, Shutdown + +function Startup() +{ + gDownloadsView = document.getElementById("downloadView"); + gSearchBox = document.getElementById("searchbox"); + + // convert strings to those in the string bundle + let sb = document.getElementById("downloadStrings"); + let getStr = string => sb.getString(string); + for (let [name, value] of Object.entries(gStr)) + gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr); + + initStatement(); + buildDownloadList(true); + + // The DownloadProgressListener (DownloadProgressListener.js) handles progress + // notifications. + gDownloadListener = new DownloadProgressListener(); + gDownloadManager.addListener(gDownloadListener); + + // If the UI was displayed because the user interacted, we need to make sure + // we update gUserInteracted accordingly. + if (window.arguments[1] == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED) + gUserInteracted = true; + + // downloads can finish before Startup() does, so check if the window should + // close and act accordingly + if (!autoRemoveAndClose()) + gDownloadsView.focus(); + + let obs = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + obs.addObserver(gDownloadObserver, "download-manager-remove-download", false); + obs.addObserver(gDownloadObserver, "browser-lastwindow-close-granted", false); + + // Clear the search box and move focus to the list on escape from the box + gSearchBox.addEventListener("keypress", function(e) { + if (e.keyCode == e.DOM_VK_ESCAPE) { + // Move focus to the list instead of closing the window + gDownloadsView.focus(); + e.preventDefault(); + } + }, false); + + let DownloadTaskbarProgress = + Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress; + DownloadTaskbarProgress.onDownloadWindowLoad(window); +} + +function Shutdown() +{ + gDownloadManager.removeListener(gDownloadListener); + + let obs = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + obs.removeObserver(gDownloadObserver, "download-manager-remove-download"); + obs.removeObserver(gDownloadObserver, "browser-lastwindow-close-granted"); + + clearTimeout(gBuilder); + gStmt.reset(); + gStmt.finalize(); +} + +var gDownloadObserver = { + observe: function gdo_observe(aSubject, aTopic, aData) { + switch (aTopic) { + case "download-manager-remove-download": + // A null subject here indicates "remove multiple", so we just rebuild. + if (!aSubject) { + // Rebuild the default view + buildDownloadList(true); + break; + } + + // Otherwise, remove a single download + let id = aSubject.QueryInterface(Ci.nsISupportsPRUint32); + let dl = getDownload(id.data); + removeFromView(dl); + break; + case "browser-lastwindow-close-granted": + if (AppConstants.platform != "macosx" && + gDownloadManager.activeDownloadCount == 0) { + setTimeout(gCloseDownloadManager, 0); + } + break; + } + } +}; + +// View Context Menus + +var gContextMenus = [ + // DOWNLOAD_DOWNLOADING + [ + "menuitem_pause" + , "menuitem_cancel" + , "menuseparator" + , "menuitem_show" + , "menuseparator" + , "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + ], + // DOWNLOAD_FINISHED + [ + "menuitem_open" + , "menuitem_show" + , "menuseparator" + , "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + , "menuseparator" + , "menuitem_removeFromList" + ], + // DOWNLOAD_FAILED + [ + "menuitem_retry" + , "menuseparator" + , "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + , "menuseparator" + , "menuitem_removeFromList" + ], + // DOWNLOAD_CANCELED + [ + "menuitem_retry" + , "menuseparator" + , "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + , "menuseparator" + , "menuitem_removeFromList" + ], + // DOWNLOAD_PAUSED + [ + "menuitem_resume" + , "menuitem_cancel" + , "menuseparator" + , "menuitem_show" + , "menuseparator" + , "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + ], + // DOWNLOAD_QUEUED + [ + "menuitem_cancel" + , "menuseparator" + , "menuitem_show" + , "menuseparator" + , "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + ], + // DOWNLOAD_BLOCKED_PARENTAL + [ + "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + , "menuseparator" + , "menuitem_removeFromList" + ], + // DOWNLOAD_SCANNING + [ + "menuitem_show" + , "menuseparator" + , "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + ], + // DOWNLOAD_DIRTY + [ + "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + , "menuseparator" + , "menuitem_removeFromList" + ], + // DOWNLOAD_BLOCKED_POLICY + [ + "menuitem_openReferrer" + , "menuitem_copyLocation" + , "menuseparator" + , "menuitem_selectAll" + , "menuseparator" + , "menuitem_removeFromList" + ] +]; + +function buildContextMenu(aEvent) +{ + if (aEvent.target.id != "downloadContextMenu") + return false; + + var popup = document.getElementById("downloadContextMenu"); + while (popup.hasChildNodes()) + popup.removeChild(popup.firstChild); + + if (gDownloadsView.selectedItem) { + let dl = gDownloadsView.selectedItem; + let idx = parseInt(dl.getAttribute("state")); + if (idx < 0) + idx = 0; + + var menus = gContextMenus[idx]; + for (let i = 0; i < menus.length; ++i) { + let menuitem = document.getElementById(menus[i]).cloneNode(true); + let cmd = menuitem.getAttribute("cmd"); + if (cmd) + menuitem.disabled = !gDownloadViewController.isCommandEnabled(cmd, dl); + + popup.appendChild(menuitem); + } + + return true; + } + + return false; +} +// Drag and Drop +var gDownloadDNDObserver = +{ + onDragStart: function (aEvent) + { + if (!gDownloadsView.selectedItem) + return; + var dl = gDownloadsView.selectedItem; + var f = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); + if (!f.exists()) + return; + + var dt = aEvent.dataTransfer; + dt.mozSetDataAt("application/x-moz-file", f, 0); + var url = Services.io.newFileURI(f).spec; + dt.setData("text/uri-list", url); + dt.setData("text/plain", url); + dt.effectAllowed = "copyMove"; + dt.addElement(dl); + }, + + onDragOver: function (aEvent) + { + var types = aEvent.dataTransfer.types; + if (types.includes("text/uri-list") || + types.includes("text/x-moz-url") || + types.includes("text/plain")) + aEvent.preventDefault(); + }, + + onDrop: function(aEvent) + { + var dt = aEvent.dataTransfer; + // If dragged item is from our source, do not try to + // redownload already downloaded file. + if (dt.mozGetDataAt("application/x-moz-file", 0)) + return; + + var url = dt.getData("URL"); + var name; + if (!url) { + url = dt.getData("text/x-moz-url") || dt.getData("text/plain"); + [url, name] = url.split("\n"); + } + if (url) { + let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; + saveURL(url, name ? name : url, null, true, true, null, sourceDoc); + } + } +} + +function pasteHandler() { + let trans = Cc["@mozilla.org/widget/transferable;1"]. + createInstance(Ci.nsITransferable); + trans.init(null); + let flavors = ["text/x-moz-url", "text/unicode"]; + flavors.forEach(trans.addDataFlavor); + + Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); + + // Getting the data or creating the nsIURI might fail + try { + let data = {}; + trans.getAnyTransferData({}, data, {}); + let [url, name] = data.value.QueryInterface(Ci.nsISupportsString).data.split("\n"); + + if (!url) + return; + + let uri = Services.io.newURI(url, null, null); + + saveURL(uri.spec, name || uri.spec, null, true, true, null, document); + } catch (ex) {} +} + +// Command Updating and Command Handlers + +var gDownloadViewController = { + isCommandEnabled: function(aCommand, aItem) + { + let dl = aItem; + let download = null; // used for getting an nsIDownload object + + switch (aCommand) { + case "cmd_cancel": + return dl.inProgress; + case "cmd_open": { + let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); + return dl.openable && file.exists(); + } + case "cmd_show": { + let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); + return file.exists(); + } + case "cmd_pause": + download = gDownloadManager.getDownload(dl.getAttribute("dlid")); + return dl.inProgress && !dl.paused && download.resumable; + case "cmd_pauseResume": + download = gDownloadManager.getDownload(dl.getAttribute("dlid")); + return (dl.inProgress || dl.paused) && download.resumable; + case "cmd_resume": + download = gDownloadManager.getDownload(dl.getAttribute("dlid")); + return dl.paused && download.resumable; + case "cmd_openReferrer": + return dl.hasAttribute("referrer"); + case "cmd_removeFromList": + case "cmd_retry": + return dl.removable; + case "cmd_copyLocation": + return true; + } + return false; + }, + + doCommand: function(aCommand, aItem) + { + if (this.isCommandEnabled(aCommand, aItem)) + this.commands[aCommand](aItem); + }, + + commands: { + cmd_cancel: function(aSelectedItem) { + cancelDownload(aSelectedItem); + }, + cmd_open: function(aSelectedItem) { + openDownload(aSelectedItem); + }, + cmd_openReferrer: function(aSelectedItem) { + openReferrer(aSelectedItem); + }, + cmd_pause: function(aSelectedItem) { + pauseDownload(aSelectedItem); + }, + cmd_pauseResume: function(aSelectedItem) { + if (aSelectedItem.paused) + this.cmd_resume(aSelectedItem); + else + this.cmd_pause(aSelectedItem); + }, + cmd_removeFromList: function(aSelectedItem) { + removeDownload(aSelectedItem); + }, + cmd_resume: function(aSelectedItem) { + resumeDownload(aSelectedItem); + }, + cmd_retry: function(aSelectedItem) { + retryDownload(aSelectedItem); + }, + cmd_show: function(aSelectedItem) { + showDownload(aSelectedItem); + }, + cmd_copyLocation: function(aSelectedItem) { + copySourceLocation(aSelectedItem); + }, + } +}; + +/** + * Helper function to do commands. + * + * @param aCmd + * The command to be performed. + * @param aItem + * The richlistitem that represents the download that will have the + * command performed on it. If this is null, the command is performed on + * all downloads. If the item passed in is not a richlistitem that + * represents a download, it will walk up the parent nodes until it finds + * a DOM node that is. + */ +function performCommand(aCmd, aItem) +{ + let elm = aItem; + if (!elm) { + // If we don't have a desired download item, do the command for all + // selected items. Initialize the callback to null so commands know to add + // a callback if they want. We will call the callback with empty arguments + // after performing the command on each selected download item. + gPerformAllCallback = null; + + // Convert the nodelist into an array to keep a copy of the download items + let items = []; + for (let i = gDownloadsView.selectedItems.length; --i >= 0; ) + items.unshift(gDownloadsView.selectedItems[i]); + + // Do the command for each download item + for (let item of items) + performCommand(aCmd, item); + + // Call the callback with no arguments and reset because we're done + if (typeof gPerformAllCallback == "function") + gPerformAllCallback(); + gPerformAllCallback = undefined; + + return; + } + while (elm.nodeName != "richlistitem" || + elm.getAttribute("type") != "download") { + elm = elm.parentNode; + } + + gDownloadViewController.doCommand(aCmd, elm); +} + +function setSearchboxFocus() +{ + gSearchBox.focus(); + gSearchBox.select(); +} + +function openExternal(aFile) +{ + var uri = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService).newFileURI(aFile); + + var protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. + getService(Ci.nsIExternalProtocolService); + protocolSvc.loadUrl(uri); + + return; +} + +// Utility Functions + +/** + * Create a download richlistitem with the provided attributes. Some attributes + * are *required* while optional ones will only be set on the item if provided. + * + * @param aAttrs + * An object that must have the following properties: dlid, file, + * target, uri, state, progress, startTime, endTime, currBytes, + * maxBytes; optional properties: referrer + * @return An initialized download richlistitem + */ +function createDownloadItem(aAttrs) +{ + let dl = document.createElement("richlistitem"); + + // Copy the attributes from the argument into the item + for (let attr in aAttrs) + dl.setAttribute(attr, aAttrs[attr]); + + // Initialize other attributes + dl.setAttribute("type", "download"); + dl.setAttribute("id", "dl" + aAttrs.dlid); + dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32"); + dl.setAttribute("lastSeconds", Infinity); + + // Initialize more complex attributes + updateTime(dl); + updateStatus(dl); + + try { + let file = getLocalFileFromNativePathOrUrl(aAttrs.file); + dl.setAttribute("path", file.nativePath || file.path); + return dl; + } catch (e) { + // aFile might not be a file: url or a valid native path + // see bug #392386 for details + } + return null; +} + +/** + * Updates the disabled state of the buttons of a downlaod. + * + * @param aItem + * The richlistitem representing the download. + */ +function updateButtons(aItem) +{ + let buttons = aItem.buttons; + + for (let i = 0; i < buttons.length; ++i) { + let cmd = buttons[i].getAttribute("cmd"); + let enabled = gDownloadViewController.isCommandEnabled(cmd, aItem); + buttons[i].disabled = !enabled; + + if ("cmd_pause" == cmd && !enabled) { + // We need to add the tooltip indicating that the download cannot be + // paused now. + buttons[i].setAttribute("tooltiptext", gStr.cannotPause); + } + } +} + +/** + * Updates the status for a download item depending on its state + * + * @param aItem + * The richlistitem that has various download attributes. + * @param aDownload + * The nsDownload from the backend. This is an optional parameter, but + * is useful for certain states such as DOWNLOADING. + */ +function updateStatus(aItem, aDownload) { + let status = ""; + let statusTip = ""; + + let state = Number(aItem.getAttribute("state")); + switch (state) { + case nsIDM.DOWNLOAD_PAUSED: + { + let currBytes = Number(aItem.getAttribute("currBytes")); + let maxBytes = Number(aItem.getAttribute("maxBytes")); + + let transfer = DownloadUtils.getTransferTotal(currBytes, maxBytes); + status = replaceInsert(gStr.paused, 1, transfer); + + break; + } + case nsIDM.DOWNLOAD_DOWNLOADING: + { + let currBytes = Number(aItem.getAttribute("currBytes")); + let maxBytes = Number(aItem.getAttribute("maxBytes")); + // If we don't have an active download, assume 0 bytes/sec + let speed = aDownload ? aDownload.speed : 0; + let lastSec = Number(aItem.getAttribute("lastSeconds")); + + let newLast; + [status, newLast] = + DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec); + + // Update lastSeconds to be the new value + aItem.setAttribute("lastSeconds", newLast); + + break; + } + case nsIDM.DOWNLOAD_FINISHED: + case nsIDM.DOWNLOAD_FAILED: + case nsIDM.DOWNLOAD_CANCELED: + case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: + case nsIDM.DOWNLOAD_BLOCKED_POLICY: + case nsIDM.DOWNLOAD_DIRTY: + { + let stateSize = {}; + stateSize[nsIDM.DOWNLOAD_FINISHED] = function() { + // Display the file size, but show "Unknown" for negative sizes + let fileSize = Number(aItem.getAttribute("maxBytes")); + let sizeText = gStr.doneSizeUnknown; + if (fileSize >= 0) { + let [size, unit] = DownloadUtils.convertByteUnits(fileSize); + sizeText = replaceInsert(gStr.doneSize, 1, size); + sizeText = replaceInsert(sizeText, 2, unit); + } + return sizeText; + }; + stateSize[nsIDM.DOWNLOAD_FAILED] = () => gStr.stateFailed; + stateSize[nsIDM.DOWNLOAD_CANCELED] = () => gStr.stateCanceled; + stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = () => gStr.stateBlockedParentalControls; + stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = () => gStr.stateBlockedPolicy; + stateSize[nsIDM.DOWNLOAD_DIRTY] = () => gStr.stateDirty; + + // Insert 1 is the download size or download state + status = replaceInsert(gStr.doneStatus, 1, stateSize[state]()); + + let [displayHost, fullHost] = + DownloadUtils.getURIHost(getReferrerOrSource(aItem)); + + // Insert 2 is the eTLD + 1 or other variations of the host + status = replaceInsert(status, 2, displayHost); + // Set the tooltip to be the full host + statusTip = fullHost; + + break; + } + } + + aItem.setAttribute("status", status); + aItem.setAttribute("statusTip", statusTip != "" ? statusTip : status); +} + +/** + * Updates the time that gets shown for completed download items + * + * @param aItem + * The richlistitem representing a download in the UI + */ +function updateTime(aItem) +{ + // Don't bother updating for things that aren't finished + if (aItem.inProgress) + return; + + let end = new Date(parseInt(aItem.getAttribute("endTime"))); + let [dateCompact, dateComplete] = DownloadUtils.getReadableDates(end); + aItem.setAttribute("dateTime", dateCompact); + aItem.setAttribute("dateTimeTip", dateComplete); +} + +/** + * Helper function to replace a placeholder string with a real string + * + * @param aText + * Source text containing placeholder (e.g., #1) + * @param aIndex + * Index number of placeholder to replace + * @param aValue + * New string to put in place of placeholder + * @return The string with placeholder replaced with the new string + */ +function replaceInsert(aText, aIndex, aValue) +{ + return aText.replace("#" + aIndex, aValue); +} + +/** + * Perform the default action for the currently selected download item + */ +function doDefaultForSelected() +{ + // Make sure we have something selected + let item = gDownloadsView.selectedItem; + if (!item) + return; + + // Get the default action (first item in the menu) + let state = Number(item.getAttribute("state")); + let menuitem = document.getElementById(gContextMenus[state][0]); + + // Try to do the action if the command is enabled + gDownloadViewController.doCommand(menuitem.getAttribute("cmd"), item); +} + +function removeFromView(aDownload) +{ + // Make sure we have an item to remove + if (!aDownload) return; + + let index = gDownloadsView.selectedIndex; + gDownloadsView.removeChild(aDownload); + gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1); + + // We might have removed the last item, so update the clear list button + updateClearListButton(); +} + +function getReferrerOrSource(aDownload) +{ + // Give the referrer if we have it set + if (aDownload.hasAttribute("referrer")) + return aDownload.getAttribute("referrer"); + + // Otherwise, provide the source + return aDownload.getAttribute("uri"); +} + +/** + * Initiate building the download list to have the active downloads followed by + * completed ones filtered by the search term if necessary. + * + * @param aForceBuild + * Force the list to be built even if the search terms don't change + */ +function buildDownloadList(aForceBuild) +{ + // Stringify the previous search + let prevSearch = gSearchTerms.join(" "); + + // Array of space-separated lower-case search terms + gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/g, ""). + toLowerCase().split(/\s+/); + + // Unless forced, don't rebuild the download list if the search didn't change + if (!aForceBuild && gSearchTerms.join(" ") == prevSearch) + return; + + // Clear out values before using them + clearTimeout(gBuilder); + gStmt.reset(); + + // Clear the list before adding items by replacing with a shallow copy + let empty = gDownloadsView.cloneNode(false); + gDownloadsView.parentNode.replaceChild(empty, gDownloadsView); + gDownloadsView = empty; + + try { + gStmt.bindByIndex(0, nsIDM.DOWNLOAD_NOTSTARTED); + gStmt.bindByIndex(1, nsIDM.DOWNLOAD_DOWNLOADING); + gStmt.bindByIndex(2, nsIDM.DOWNLOAD_PAUSED); + gStmt.bindByIndex(3, nsIDM.DOWNLOAD_QUEUED); + gStmt.bindByIndex(4, nsIDM.DOWNLOAD_SCANNING); + } catch (e) { + // Something must have gone wrong when binding, so clear and quit + gStmt.reset(); + return; + } + + // Take a quick break before we actually start building the list + gBuilder = setTimeout(function() { + // Start building the list + stepListBuilder(1); + + // We just tried to add a single item, so we probably need to enable + updateClearListButton(); + }, 0); +} + +/** + * Incrementally build the download list by adding at most the requested number + * of items if there are items to add. After doing that, it will schedule + * another chunk of items specified by gListBuildDelay and gListBuildChunk. + * + * @param aNumItems + * Number of items to add to the list before taking a break + */ +function stepListBuilder(aNumItems) { + try { + // If we're done adding all items, we can quit + if (!gStmt.executeStep()) { + // Send a notification that we finished, but wait for clear list to update + updateClearListButton(); + setTimeout(() => Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService). + notifyObservers(window, "download-manager-ui-done", null), 0); + + return; + } + + // Try to get the attribute values from the statement + let attrs = { + dlid: gStmt.getInt64(0), + file: gStmt.getString(1), + target: gStmt.getString(2), + uri: gStmt.getString(3), + state: gStmt.getInt32(4), + startTime: Math.round(gStmt.getInt64(5) / 1000), + endTime: Math.round(gStmt.getInt64(6) / 1000), + currBytes: gStmt.getInt64(8), + maxBytes: gStmt.getInt64(9) + }; + + // Only add the referrer if it's not null + let referrer = gStmt.getString(7); + if (referrer) + attrs.referrer = referrer; + + // If the download is active, grab the real progress, otherwise default 100 + let isActive = gStmt.getInt32(10); + attrs.progress = isActive ? gDownloadManager.getDownload(attrs.dlid). + percentComplete : 100; + + // Make the item and add it to the end if it's active or matches the search + let item = createDownloadItem(attrs); + if (item && (isActive || downloadMatchesSearch(item))) { + // Add item to the end + gDownloadsView.appendChild(item); + + // Because of the joys of XBL, we can't update the buttons until the + // download object is in the document. + updateButtons(item); + } else { + // We didn't add an item, so bump up the number of items to process, but + // not a whole number so that we eventually do pause for a chunk break + aNumItems += .9; + } + } catch (e) { + // Something went wrong when stepping or getting values, so clear and quit + gStmt.reset(); + return; + } + + // Add another item to the list if we should; otherwise, let the UI update + // and continue later + if (aNumItems > 1) { + stepListBuilder(aNumItems - 1); + } else { + // Use a shorter delay for earlier downloads to display them faster + let delay = Math.min(gDownloadsView.itemCount * 10, gListBuildDelay); + gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk); + } +} + +/** + * Add a download to the front of the download list + * + * @param aDownload + * The nsIDownload to make into a richlistitem + */ +function prependList(aDownload) +{ + let attrs = { + dlid: aDownload.id, + file: aDownload.target.spec, + target: aDownload.displayName, + uri: aDownload.source.spec, + state: aDownload.state, + progress: aDownload.percentComplete, + startTime: Math.round(aDownload.startTime / 1000), + endTime: Date.now(), + currBytes: aDownload.amountTransferred, + maxBytes: aDownload.size + }; + + // Make the item and add it to the beginning + let item = createDownloadItem(attrs); + if (item) { + // Add item to the beginning + gDownloadsView.insertBefore(item, gDownloadsView.firstChild); + + // Because of the joys of XBL, we can't update the buttons until the + // download object is in the document. + updateButtons(item); + + // We might have added an item to an empty list, so update button + updateClearListButton(); + } +} + +/** + * Check if the download matches the current search term based on the texts + * shown to the user. All search terms are checked to see if each matches any + * of the displayed texts. + * + * @param aItem + * Download richlistitem to check if it matches the current search + * @return Boolean true if it matches the search; false otherwise + */ +function downloadMatchesSearch(aItem) +{ + // Search through the download attributes that are shown to the user and + // make it into one big string for easy combined searching + let combinedSearch = ""; + for (let attr of gSearchAttributes) + combinedSearch += aItem.getAttribute(attr).toLowerCase() + " "; + + // Make sure each of the terms are found + for (let term of gSearchTerms) + if (combinedSearch.indexOf(term) == -1) + return false; + + return true; +} + +// we should be using real URLs all the time, but until +// bug 239948 is fully fixed, this will do... +// +// note, this will thrown an exception if the native path +// is not valid (for example a native Windows path on a Mac) +// see bug #392386 for details +function getLocalFileFromNativePathOrUrl(aPathOrUrl) +{ + if (aPathOrUrl.substring(0, 7) == "file://") { + // if this is a URL, get the file from that + let ioSvc = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + + // XXX it's possible that using a null char-set here is bad + const fileUrl = ioSvc.newURI(aPathOrUrl, null, null). + QueryInterface(Ci.nsIFileURL); + return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile); + } + // if it's a pathname, create the nsILocalFile directly + var f = new nsLocalFile(aPathOrUrl); + + return f; +} + +/** + * Update the disabled state of the clear list button based on whether or not + * there are items in the list that can potentially be removed. + */ +function updateClearListButton() +{ + let button = document.getElementById("clearListButton"); + // The button is enabled if we have items in the list and we can clean up + button.disabled = !(gDownloadsView.itemCount && gDownloadManager.canCleanUp); +} + +function getDownload(aID) +{ + return document.getElementById("dl" + aID); +} + +/** + * Initialize the statement which is used to retrieve the list of downloads. + */ +function initStatement() +{ + if (gStmt) + gStmt.finalize(); + + gStmt = gDownloadManager.DBConnection.createStatement( + "SELECT id, target, name, source, state, startTime, endTime, referrer, " + + "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " + + "FROM moz_downloads " + + "ORDER BY isActive DESC, endTime DESC, startTime DESC"); +} diff --git a/toolkit/mozapps/downloads/content/downloads.xul b/toolkit/mozapps/downloads/content/downloads.xul new file mode 100644 index 000000000..5ca9eec2d --- /dev/null +++ b/toolkit/mozapps/downloads/content/downloads.xul @@ -0,0 +1,164 @@ + + +# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# 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/. + +#ifdef XP_UNIX +#ifndef XP_MACOSX +#define XP_GNOME 1 +#endif +#endif + + + + + + +%downloadManagerDTD; + +%editMenuOverlayDTD; +]> + + + + + + +

+ +

+  
+
+  
+
diff --git a/toolkit/mozapps/downloads/tests/chrome/test_unknownContentType_dialog_layout.xul b/toolkit/mozapps/downloads/tests/chrome/test_unknownContentType_dialog_layout.xul new file mode 100644 index 000000000..1210b908d --- /dev/null +++ b/toolkit/mozapps/downloads/tests/chrome/test_unknownContentType_dialog_layout.xul @@ -0,0 +1,108 @@ + + + + + + + + + +

+ +

+  
+
+  
+
diff --git a/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif new file mode 100644 index 000000000..9353d1312 --- /dev/null +++ b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif @@ -0,0 +1 @@ +Dummy content for unknownContentType_dialog_layout_data.pif diff --git a/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^ b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^ new file mode 100644 index 000000000..09b22facc --- /dev/null +++ b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^ @@ -0,0 +1 @@ +Content-Type: application/octet-stream diff --git a/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt new file mode 100644 index 000000000..77e719559 --- /dev/null +++ b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt @@ -0,0 +1 @@ +Dummy content for unknownContentType_dialog_layout_data.txt diff --git a/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt^headers^ b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt^headers^ new file mode 100644 index 000000000..2a3c472e2 --- /dev/null +++ b/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt^headers^ @@ -0,0 +1,2 @@ +Content-Type: text/plain +Content-Disposition: attachment diff --git a/toolkit/mozapps/downloads/tests/moz.build b/toolkit/mozapps/downloads/tests/moz.build new file mode 100644 index 000000000..a4b1efb9a --- /dev/null +++ b/toolkit/mozapps/downloads/tests/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] +MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini'] diff --git a/toolkit/mozapps/downloads/tests/unit/.eslintrc.js b/toolkit/mozapps/downloads/tests/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/mozapps/downloads/tests/unit/head_downloads.js b/toolkit/mozapps/downloads/tests/unit/head_downloads.js new file mode 100644 index 000000000..4f199e5cf --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/head_downloads.js @@ -0,0 +1,5 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +do_register_cleanup(function() { + Services.obs.notifyObservers(null, "quit-application", null); +}); diff --git a/toolkit/mozapps/downloads/tests/unit/test_DownloadPaths.js b/toolkit/mozapps/downloads/tests/unit/test_DownloadPaths.js new file mode 100644 index 000000000..77249169d --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/test_DownloadPaths.js @@ -0,0 +1,131 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Tests for the "DownloadPaths.jsm" JavaScript module. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/DownloadPaths.jsm"); + +/** + * Provides a temporary save directory. + * + * @returns nsIFile pointing to the new or existing directory. + */ +function createTemporarySaveDirectory() +{ + var saveDir = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + return saveDir; +} + +function testSplitBaseNameAndExtension(aLeafName, [aBase, aExt]) +{ + var [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName); + do_check_eq(base, aBase); + do_check_eq(ext, aExt); + + // If we modify the base name and concatenate it with the extension again, + // another roundtrip through the function should give a consistent result. + // The only exception is when we introduce an extension in a file name that + // didn't have one or that ended with one of the special cases like ".gz". If + // we avoid using a dot and we introduce at least another special character, + // the results are always consistent. + [base, ext] = DownloadPaths.splitBaseNameAndExtension("(" + base + ")" + ext); + do_check_eq(base, "(" + aBase + ")"); + do_check_eq(ext, aExt); +} + +function testCreateNiceUniqueFile(aTempFile, aExpectedLeafName) +{ + var createdFile = DownloadPaths.createNiceUniqueFile(aTempFile); + do_check_eq(createdFile.leafName, aExpectedLeafName); +} + +function run_test() +{ + // Usual file names. + testSplitBaseNameAndExtension("base", ["base", ""]); + testSplitBaseNameAndExtension("base.ext", ["base", ".ext"]); + testSplitBaseNameAndExtension("base.application", ["base", ".application"]); + testSplitBaseNameAndExtension("base.x.Z", ["base", ".x.Z"]); + testSplitBaseNameAndExtension("base.ext.Z", ["base", ".ext.Z"]); + testSplitBaseNameAndExtension("base.ext.gz", ["base", ".ext.gz"]); + testSplitBaseNameAndExtension("base.ext.Bz2", ["base", ".ext.Bz2"]); + testSplitBaseNameAndExtension("base..ext", ["base.", ".ext"]); + testSplitBaseNameAndExtension("base..Z", ["base.", ".Z"]); + testSplitBaseNameAndExtension("base. .Z", ["base. ", ".Z"]); + testSplitBaseNameAndExtension("base.base.Bz2", ["base.base", ".Bz2"]); + testSplitBaseNameAndExtension("base .ext", ["base ", ".ext"]); + + // Corner cases. A name ending with a dot technically has no extension, but + // we consider the ending dot separately from the base name so that modifying + // the latter never results in an extension being introduced accidentally. + // Names beginning with a dot are hidden files on Unix-like platforms and if + // their name doesn't contain another dot they should have no extension, but + // on Windows the whole name is considered as an extension. + testSplitBaseNameAndExtension("base.", ["base", "."]); + testSplitBaseNameAndExtension(".ext", ["", ".ext"]); + + // Unusual file names (not recommended as input to the function). + testSplitBaseNameAndExtension("base. ", ["base", ". "]); + testSplitBaseNameAndExtension("base ", ["base ", ""]); + testSplitBaseNameAndExtension("", ["", ""]); + testSplitBaseNameAndExtension(" ", [" ", ""]); + testSplitBaseNameAndExtension(" . ", [" ", ". "]); + testSplitBaseNameAndExtension(" .. ", [" .", ". "]); + testSplitBaseNameAndExtension(" .ext", [" ", ".ext"]); + testSplitBaseNameAndExtension(" .ext. ", [" .ext", ". "]); + testSplitBaseNameAndExtension(" .ext.gz ", [" .ext", ".gz "]); + + var destDir = createTemporarySaveDirectory(); + try { + // Single extension. + var tempFile = destDir.clone(); + tempFile.append("test.txt"); + testCreateNiceUniqueFile(tempFile, "test.txt"); + testCreateNiceUniqueFile(tempFile, "test(1).txt"); + testCreateNiceUniqueFile(tempFile, "test(2).txt"); + + // Double extension. + tempFile.leafName = "test.tar.gz"; + testCreateNiceUniqueFile(tempFile, "test.tar.gz"); + testCreateNiceUniqueFile(tempFile, "test(1).tar.gz"); + testCreateNiceUniqueFile(tempFile, "test(2).tar.gz"); + + // Test automatic shortening of long file names. We don't know exactly how + // many characters are removed, because it depends on the name of the folder + // where the file is located. + tempFile.leafName = new Array(256).join("T") + ".txt"; + var newFile = DownloadPaths.createNiceUniqueFile(tempFile); + do_check_true(newFile.leafName.length < tempFile.leafName.length); + do_check_eq(newFile.leafName.slice(-4), ".txt"); + + // Creating a valid file name from an invalid one is not always possible. + tempFile.append("file-under-long-directory.txt"); + try { + DownloadPaths.createNiceUniqueFile(tempFile); + do_throw("Exception expected with a long parent directory name.") + } catch (e) { + // An exception is expected, but we don't know which one exactly. + } + } finally { + // Clean up the temporary directory. + destDir.remove(true); + } +} diff --git a/toolkit/mozapps/downloads/tests/unit/test_DownloadUtils.js b/toolkit/mozapps/downloads/tests/unit/test_DownloadUtils.js new file mode 100644 index 000000000..11e7776a7 --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/test_DownloadUtils.js @@ -0,0 +1,237 @@ +/* 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/. */ + +var Cu = Components.utils; +Cu.import("resource://gre/modules/DownloadUtils.jsm"); + +const gDecimalSymbol = Number(5.4).toLocaleString().match(/\D/); +function _(str) { + return str.replace(/\./g, gDecimalSymbol); +} + +function testConvertByteUnits(aBytes, aValue, aUnit) +{ + let [value, unit] = DownloadUtils.convertByteUnits(aBytes); + do_check_eq(value, aValue); + do_check_eq(unit, aUnit); +} + +function testTransferTotal(aCurrBytes, aMaxBytes, aTransfer) +{ + let transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes); + do_check_eq(transfer, aTransfer); +} + +// Get the em-dash character because typing it directly here doesn't work :( +var gDash = DownloadUtils.getDownloadStatus(0)[0].match(/remaining (.) 0 bytes/)[1]; + +var gVals = [0, 100, 2345, 55555, 982341, 23194134, 1482, 58, 9921949201, 13498132, Infinity]; + +function testStatus(aFunc, aCurr, aMore, aRate, aTest) +{ + dump("Status Test: " + [aCurr, aMore, aRate, aTest] + "\n"); + let curr = gVals[aCurr]; + let max = curr + gVals[aMore]; + let speed = gVals[aRate]; + + let [status, last] = aFunc(curr, max, speed); + + if (0) { + dump("testStatus(" + aCurr + ", " + aMore + ", " + aRate + ", [\"" + + status.replace(gDash, "--") + "\", " + last.toFixed(3) + "]);\n"); + } + + // Make sure the status text matches + do_check_eq(status, _(aTest[0].replace(/--/, gDash))); + + // Make sure the lastSeconds matches + if (last == Infinity) + do_check_eq(last, aTest[1]); + else + do_check_true(Math.abs(last - aTest[1]) < .1); +} + +function testURI(aURI, aDisp, aHost) +{ + dump("URI Test: " + [aURI, aDisp, aHost] + "\n"); + + let [disp, host] = DownloadUtils.getURIHost(aURI); + + // Make sure we have the right display host and full host + do_check_eq(disp, aDisp); + do_check_eq(host, aHost); +} + + +function testGetReadableDates(aDate, aCompactValue) +{ + const now = new Date(2000, 11, 31, 11, 59, 59); + + let [dateCompact] = DownloadUtils.getReadableDates(aDate, now); + do_check_eq(dateCompact, aCompactValue); +} + +function testAllGetReadableDates() +{ + // This test cannot depend on the current date and time, or the date format. + // It depends on being run with the English localization, however. + const today_11_30 = new Date(2000, 11, 31, 11, 30, 15); + const today_12_30 = new Date(2000, 11, 31, 12, 30, 15); + const yesterday_11_30 = new Date(2000, 11, 30, 11, 30, 15); + const yesterday_12_30 = new Date(2000, 11, 30, 12, 30, 15); + const twodaysago = new Date(2000, 11, 29, 11, 30, 15); + const sixdaysago = new Date(2000, 11, 25, 11, 30, 15); + const sevendaysago = new Date(2000, 11, 24, 11, 30, 15); + + // TODO: Remove Intl fallback when bug 1215247 is fixed. + const locale = typeof Intl === "undefined" + ? undefined + : Components.classes["@mozilla.org/chrome/chrome-registry;1"] + .getService(Components.interfaces.nsIXULChromeRegistry) + .getSelectedLocale("global", true); + + let dts = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]. + getService(Components.interfaces.nsIScriptableDateFormat); + + testGetReadableDates(today_11_30, dts.FormatTime("", dts.timeFormatNoSeconds, + 11, 30, 0)); + testGetReadableDates(today_12_30, dts.FormatTime("", dts.timeFormatNoSeconds, + 12, 30, 0)); + testGetReadableDates(yesterday_11_30, "Yesterday"); + testGetReadableDates(yesterday_12_30, "Yesterday"); + testGetReadableDates(twodaysago, + typeof Intl === "undefined" + ? twodaysago.toLocaleFormat("%A") + : twodaysago.toLocaleDateString(locale, { weekday: "long" })); + testGetReadableDates(sixdaysago, + typeof Intl === "undefined" + ? sixdaysago.toLocaleFormat("%A") + : sixdaysago.toLocaleDateString(locale, { weekday: "long" })); + testGetReadableDates(sevendaysago, + (typeof Intl === "undefined" + ? sevendaysago.toLocaleFormat("%B") + : sevendaysago.toLocaleDateString(locale, { month: "long" })) + " " + + sevendaysago.getDate().toString().padStart(2, "0")); + + let [, dateTimeFull] = DownloadUtils.getReadableDates(today_11_30); + do_check_eq(dateTimeFull, dts.FormatDateTime("", dts.dateFormatLong, + dts.timeFormatNoSeconds, + 2000, 12, 31, 11, 30, 0)); +} + +function run_test() +{ + testConvertByteUnits(-1, "-1", "bytes"); + testConvertByteUnits(1, _("1"), "bytes"); + testConvertByteUnits(42, _("42"), "bytes"); + testConvertByteUnits(123, _("123"), "bytes"); + testConvertByteUnits(1024, _("1.0"), "KB"); + testConvertByteUnits(8888, _("8.7"), "KB"); + testConvertByteUnits(59283, _("57.9"), "KB"); + testConvertByteUnits(640000, _("625"), "KB"); + testConvertByteUnits(1048576, _("1.0"), "MB"); + testConvertByteUnits(307232768, _("293"), "MB"); + testConvertByteUnits(1073741824, _("1.0"), "GB"); + + testTransferTotal(1, 1, _("1 of 1 bytes")); + testTransferTotal(234, 4924, _("234 bytes of 4.8 KB")); + testTransferTotal(94923, 233923, _("92.7 of 228 KB")); + testTransferTotal(4924, 94923, _("4.8 of 92.7 KB")); + testTransferTotal(2342, 294960345, _("2.3 KB of 281 MB")); + testTransferTotal(234, undefined, _("234 bytes")); + testTransferTotal(4889023, undefined, _("4.7 MB")); + + if (0) { + // Help find some interesting test cases + let r = () => Math.floor(Math.random() * 10); + for (let i = 0; i < 100; i++) { + testStatus(r(), r(), r()); + } + } + + // First, test with rates, via getDownloadStatus... + let statusFunc = DownloadUtils.getDownloadStatus.bind(DownloadUtils); + + testStatus(statusFunc, 2, 1, 7, ["A few seconds remaining -- 2.3 of 2.4 KB (58 bytes/sec)", 1.724]); + testStatus(statusFunc, 1, 2, 6, ["A few seconds remaining -- 100 bytes of 2.4 KB (1.4 KB/sec)", 1.582]); + testStatus(statusFunc, 4, 3, 9, ["A few seconds remaining -- 959 KB of 1.0 MB (12.9 MB/sec)", 0.004]); + testStatus(statusFunc, 2, 3, 8, ["A few seconds remaining -- 2.3 of 56.5 KB (9.2 GB/sec)", 0.000]); + + testStatus(statusFunc, 8, 4, 3, ["17 seconds remaining -- 9.2 of 9.2 GB (54.3 KB/sec)", 17.682]); + testStatus(statusFunc, 1, 3, 2, ["23 seconds remaining -- 100 bytes of 54.4 KB (2.3 KB/sec)", 23.691]); + testStatus(statusFunc, 9, 3, 2, ["23 seconds remaining -- 12.9 of 12.9 MB (2.3 KB/sec)", 23.691]); + testStatus(statusFunc, 5, 6, 7, ["25 seconds remaining -- 22.1 of 22.1 MB (58 bytes/sec)", 25.552]); + + testStatus(statusFunc, 3, 9, 3, ["4 minutes remaining -- 54.3 KB of 12.9 MB (54.3 KB/sec)", 242.969]); + testStatus(statusFunc, 2, 3, 1, ["9 minutes remaining -- 2.3 of 56.5 KB (100 bytes/sec)", 555.550]); + testStatus(statusFunc, 4, 3, 7, ["15 minutes remaining -- 959 KB of 1.0 MB (58 bytes/sec)", 957.845]); + testStatus(statusFunc, 5, 3, 7, ["15 minutes remaining -- 22.1 of 22.2 MB (58 bytes/sec)", 957.845]); + + testStatus(statusFunc, 1, 9, 2, ["1 hour, 35 minutes remaining -- 100 bytes of 12.9 MB (2.3 KB/sec)", 5756.133]); + testStatus(statusFunc, 2, 9, 6, ["2 hours, 31 minutes remaining -- 2.3 KB of 12.9 MB (1.4 KB/sec)", 9108.051]); + testStatus(statusFunc, 2, 4, 1, ["2 hours, 43 minutes remaining -- 2.3 of 962 KB (100 bytes/sec)", 9823.410]); + testStatus(statusFunc, 6, 4, 7, ["4 hours, 42 minutes remaining -- 1.4 of 961 KB (58 bytes/sec)", 16936.914]); + + testStatus(statusFunc, 6, 9, 1, ["1 day, 13 hours remaining -- 1.4 KB of 12.9 MB (100 bytes/sec)", 134981.320]); + testStatus(statusFunc, 3, 8, 3, ["2 days, 1 hour remaining -- 54.3 KB of 9.2 GB (54.3 KB/sec)", 178596.872]); + testStatus(statusFunc, 1, 8, 6, ["77 days, 11 hours remaining -- 100 bytes of 9.2 GB (1.4 KB/sec)", 6694972.470]); + testStatus(statusFunc, 6, 8, 7, ["1979 days, 22 hours remaining -- 1.4 KB of 9.2 GB (58 bytes/sec)", 171068089.672]); + + testStatus(statusFunc, 0, 0, 5, ["Unknown time remaining -- 0 of 0 bytes (22.1 MB/sec)", Infinity]); + testStatus(statusFunc, 0, 6, 0, ["Unknown time remaining -- 0 bytes of 1.4 KB (0 bytes/sec)", Infinity]); + testStatus(statusFunc, 6, 6, 0, ["Unknown time remaining -- 1.4 of 2.9 KB (0 bytes/sec)", Infinity]); + testStatus(statusFunc, 8, 5, 0, ["Unknown time remaining -- 9.2 of 9.3 GB (0 bytes/sec)", Infinity]); + + // With rate equal to Infinity + testStatus(statusFunc, 0, 0, 10, ["Unknown time remaining -- 0 of 0 bytes (Really fast)", Infinity]); + testStatus(statusFunc, 1, 2, 10, ["A few seconds remaining -- 100 bytes of 2.4 KB (Really fast)", 0]); + + // Now test without rates, via getDownloadStatusNoRate. + statusFunc = DownloadUtils.getDownloadStatusNoRate.bind(DownloadUtils); + + testStatus(statusFunc, 2, 1, 7, ["A few seconds remaining -- 2.3 of 2.4 KB", 1.724]); + testStatus(statusFunc, 1, 2, 6, ["A few seconds remaining -- 100 bytes of 2.4 KB", 1.582]); + testStatus(statusFunc, 4, 3, 9, ["A few seconds remaining -- 959 KB of 1.0 MB", 0.004]); + testStatus(statusFunc, 2, 3, 8, ["A few seconds remaining -- 2.3 of 56.5 KB", 0.000]); + + testStatus(statusFunc, 8, 4, 3, ["17 seconds remaining -- 9.2 of 9.2 GB", 17.682]); + testStatus(statusFunc, 1, 3, 2, ["23 seconds remaining -- 100 bytes of 54.4 KB", 23.691]); + testStatus(statusFunc, 9, 3, 2, ["23 seconds remaining -- 12.9 of 12.9 MB", 23.691]); + testStatus(statusFunc, 5, 6, 7, ["25 seconds remaining -- 22.1 of 22.1 MB", 25.552]); + + testStatus(statusFunc, 3, 9, 3, ["4 minutes remaining -- 54.3 KB of 12.9 MB", 242.969]); + testStatus(statusFunc, 2, 3, 1, ["9 minutes remaining -- 2.3 of 56.5 KB", 555.550]); + testStatus(statusFunc, 4, 3, 7, ["15 minutes remaining -- 959 KB of 1.0 MB", 957.845]); + testStatus(statusFunc, 5, 3, 7, ["15 minutes remaining -- 22.1 of 22.2 MB", 957.845]); + + testStatus(statusFunc, 1, 9, 2, ["1 hour, 35 minutes remaining -- 100 bytes of 12.9 MB", 5756.133]); + testStatus(statusFunc, 2, 9, 6, ["2 hours, 31 minutes remaining -- 2.3 KB of 12.9 MB", 9108.051]); + testStatus(statusFunc, 2, 4, 1, ["2 hours, 43 minutes remaining -- 2.3 of 962 KB", 9823.410]); + testStatus(statusFunc, 6, 4, 7, ["4 hours, 42 minutes remaining -- 1.4 of 961 KB", 16936.914]); + + testStatus(statusFunc, 6, 9, 1, ["1 day, 13 hours remaining -- 1.4 KB of 12.9 MB", 134981.320]); + testStatus(statusFunc, 3, 8, 3, ["2 days, 1 hour remaining -- 54.3 KB of 9.2 GB", 178596.872]); + testStatus(statusFunc, 1, 8, 6, ["77 days, 11 hours remaining -- 100 bytes of 9.2 GB", 6694972.470]); + testStatus(statusFunc, 6, 8, 7, ["1979 days, 22 hours remaining -- 1.4 KB of 9.2 GB", 171068089.672]); + + testStatus(statusFunc, 0, 0, 5, ["Unknown time remaining -- 0 of 0 bytes", Infinity]); + testStatus(statusFunc, 0, 6, 0, ["Unknown time remaining -- 0 bytes of 1.4 KB", Infinity]); + testStatus(statusFunc, 6, 6, 0, ["Unknown time remaining -- 1.4 of 2.9 KB", Infinity]); + testStatus(statusFunc, 8, 5, 0, ["Unknown time remaining -- 9.2 of 9.3 GB", Infinity]); + + testURI("http://www.mozilla.org/", "mozilla.org", "www.mozilla.org"); + testURI("http://www.city.mikasa.hokkaido.jp/", "city.mikasa.hokkaido.jp", "www.city.mikasa.hokkaido.jp"); + testURI("data:text/html,Hello World", "data resource", "data resource"); + testURI("jar:http://www.mozilla.com/file!/magic", "mozilla.com", "www.mozilla.com"); + testURI("file:///C:/Cool/Stuff/", "local file", "local file"); + // Don't test for moz-icon if we don't have a protocol handler for it (e.g. b2g): + if ("@mozilla.org/network/protocol;1?name=moz-icon" in Components.classes) { + testURI("moz-icon:file:///test.extension", "local file", "local file"); + testURI("moz-icon://.extension", "moz-icon resource", "moz-icon resource"); + } + testURI("about:config", "about resource", "about resource"); + testURI("invalid.uri", "", ""); + + testAllGetReadableDates(); +} diff --git a/toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js b/toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js new file mode 100644 index 000000000..75eff3370 --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js @@ -0,0 +1,55 @@ +/* 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/. */ + +/** + * Test bug 448344 to make sure when we're in low minutes, we show both minutes + * and seconds; but continue to show only minutes when we have plenty. + */ + +Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); + +/** + * Print some debug message to the console. All arguments will be printed, + * separated by spaces. + * + * @param [arg0, arg1, arg2, ...] + * Any number of arguments to print out + * @usage _("Hello World") -> prints "Hello World" + * @usage _(1, 2, 3) -> prints "1 2 3" + */ +var _ = function(some, debug, text, to) { + print(Array.slice(arguments).join(" ")); +}; + +_("Make an array of time lefts and expected string to be shown for that time"); +var expectedTimes = [ + [1.1, "A few seconds remaining", "under 4sec -> few"], + [2.5, "A few seconds remaining", "under 4sec -> few"], + [3.9, "A few seconds remaining", "under 4sec -> few"], + [5.3, "5 seconds remaining", "truncate seconds"], + [1.1 * 60, "1 minute, 6 seconds remaining", "under 4min -> show sec"], + [2.5 * 60, "2 minutes, 30 seconds remaining", "under 4min -> show sec"], + [3.9 * 60, "3 minutes, 54 seconds remaining", "under 4min -> show sec"], + [5.3 * 60, "5 minutes remaining", "over 4min -> only show min"], + [1.1 * 3600, "1 hour, 6 minutes remaining", "over 1hr -> show min/sec"], + [2.5 * 3600, "2 hours, 30 minutes remaining", "over 1hr -> show min/sec"], + [3.9 * 3600, "3 hours, 54 minutes remaining", "over 1hr -> show min/sec"], + [5.3 * 3600, "5 hours, 18 minutes remaining", "over 1hr -> show min/sec"], +]; +_(expectedTimes.join("\n")); + +function run_test() +{ + expectedTimes.forEach(function([time, expectStatus, comment]) { + _("Running test with time", time); + _("Test comment:", comment); + let [status, last] = DownloadUtils.getTimeLeft(time); + + _("Got status:", status, "last:", last); + _("Expecting..", expectStatus); + do_check_eq(status, expectStatus); + + _(); + }); +} diff --git a/toolkit/mozapps/downloads/tests/unit/test_syncedDownloadUtils.js b/toolkit/mozapps/downloads/tests/unit/test_syncedDownloadUtils.js new file mode 100644 index 000000000..86d810a9b --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/test_syncedDownloadUtils.js @@ -0,0 +1,26 @@ +/* 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/. */ + +/** + * Test bug 420482 by making sure multiple consumers of DownloadUtils gets the + * same time remaining time if they provide the same time left but a different + * "last time". + */ + +var Cu = Components.utils; +Cu.import("resource://gre/modules/DownloadUtils.jsm"); + +function run_test() +{ + // Simulate having multiple downloads requesting time left + let downloadTimes = {}; + for (let time of [1, 30, 60, 3456, 9999]) + downloadTimes[time] = DownloadUtils.getTimeLeft(time)[0]; + + // Pretend we're a download status bar also asking for a time left, but we're + // using a different "last sec". We need to make sure we get the same time. + let lastSec = 314; + for (let [time, text] of Object.entries(downloadTimes)) + do_check_eq(DownloadUtils.getTimeLeft(time, lastSec)[0], text); +} diff --git a/toolkit/mozapps/downloads/tests/unit/test_unspecified_arguments.js b/toolkit/mozapps/downloads/tests/unit/test_unspecified_arguments.js new file mode 100644 index 000000000..02e27c92c --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/test_unspecified_arguments.js @@ -0,0 +1,25 @@ +/* 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/. */ + +/** + * Make sure passing null and nothing to various variable-arg DownloadUtils + * methods provide the same result. + */ + +var Cu = Components.utils; +Cu.import("resource://gre/modules/DownloadUtils.jsm"); + +function run_test() +{ + do_check_eq(DownloadUtils.getDownloadStatus(1000, null, null, null) + "", + DownloadUtils.getDownloadStatus(1000) + ""); + do_check_eq(DownloadUtils.getDownloadStatus(1000, null, null) + "", + DownloadUtils.getDownloadStatus(1000, null) + ""); + + do_check_eq(DownloadUtils.getTransferTotal(1000, null) + "", + DownloadUtils.getTransferTotal(1000) + ""); + + do_check_eq(DownloadUtils.getTimeLeft(1000, null) + "", + DownloadUtils.getTimeLeft(1000) + ""); +} diff --git a/toolkit/mozapps/downloads/tests/unit/xpcshell.ini b/toolkit/mozapps/downloads/tests/unit/xpcshell.ini new file mode 100644 index 000000000..877816ef6 --- /dev/null +++ b/toolkit/mozapps/downloads/tests/unit/xpcshell.ini @@ -0,0 +1,10 @@ +[DEFAULT] +head = head_downloads.js +tail = +skip-if = toolkit == 'android' + +[test_DownloadPaths.js] +[test_DownloadUtils.js] +[test_lowMinutes.js] +[test_syncedDownloadUtils.js] +[test_unspecified_arguments.js] diff --git a/toolkit/mozapps/extensions/.eslintrc.js b/toolkit/mozapps/extensions/.eslintrc.js new file mode 100644 index 000000000..2b90bd053 --- /dev/null +++ b/toolkit/mozapps/extensions/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { // eslint-disable-line no-undef + "rules": { + // No using undeclared variables + "no-undef": "error", + } +}; diff --git a/toolkit/mozapps/extensions/AddonContentPolicy.cpp b/toolkit/mozapps/extensions/AddonContentPolicy.cpp new file mode 100644 index 000000000..90e53b2ea --- /dev/null +++ b/toolkit/mozapps/extensions/AddonContentPolicy.cpp @@ -0,0 +1,478 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonContentPolicy.h" + +#include "mozilla/dom/nsCSPUtils.h" +#include "nsCOMPtr.h" +#include "nsContentPolicyUtils.h" +#include "nsContentTypeParser.h" +#include "nsContentUtils.h" +#include "nsIConsoleService.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIEffectiveTLDService.h" +#include "nsIScriptError.h" +#include "nsIStringBundle.h" +#include "nsIUUIDGenerator.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" + +using namespace mozilla; + +/* Enforces content policies for WebExtension scopes. Currently: + * + * - Prevents loading scripts with a non-default JavaScript version. + * - Checks custom content security policies for sufficiently stringent + * script-src and object-src directives. + */ + +#define VERSIONED_JS_BLOCKED_MESSAGE \ + u"Versioned JavaScript is a non-standard, deprecated extension, and is " \ + u"not supported in WebExtension code. For alternatives, please see: " \ + u"https://developer.mozilla.org/Add-ons/WebExtensions/Tips" + +AddonContentPolicy::AddonContentPolicy() +{ +} + +AddonContentPolicy::~AddonContentPolicy() +{ +} + +NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy, nsIAddonContentPolicy) + +static nsresult +GetWindowIDFromContext(nsISupports* aContext, uint64_t *aResult) +{ + NS_ENSURE_TRUE(aContext, NS_ERROR_FAILURE); + + nsCOMPtr content = do_QueryInterface(aContext); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + nsCOMPtr document = content->OwnerDoc(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + nsCOMPtr window = document->GetInnerWindow(); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + *aResult = window->WindowID(); + return NS_OK; +} + +static nsresult +LogMessage(const nsAString &aMessage, nsIURI* aSourceURI, const nsAString &aSourceSample, + nsISupports* aContext) +{ + nsCOMPtr error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY); + + nsCString sourceName = aSourceURI->GetSpecOrDefault(); + + uint64_t windowID = 0; + GetWindowIDFromContext(aContext, &windowID); + + nsresult rv = + error->InitWithWindowID(aMessage, NS_ConvertUTF8toUTF16(sourceName), + aSourceSample, 0, 0, nsIScriptError::errorFlag, + "JavaScript", windowID); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY); + + console->LogMessage(error); + return NS_OK; +} + + +// Content policy enforcement: + +NS_IMETHODIMP +AddonContentPolicy::ShouldLoad(uint32_t aContentType, + nsIURI* aContentLocation, + nsIURI* aRequestOrigin, + nsISupports* aContext, + const nsACString& aMimeTypeGuess, + nsISupports* aExtra, + nsIPrincipal* aRequestPrincipal, + int16_t* aShouldLoad) +{ + MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), + "We should only see external content policy types here."); + + *aShouldLoad = nsIContentPolicy::ACCEPT; + + if (!aRequestOrigin) { + return NS_OK; + } + + // Only apply this policy to requests from documents loaded from + // moz-extension URLs, or to resources being loaded from moz-extension URLs. + bool equals; + if (!((NS_SUCCEEDED(aContentLocation->SchemeIs("moz-extension", &equals)) && equals) || + (NS_SUCCEEDED(aRequestOrigin->SchemeIs("moz-extension", &equals)) && equals))) { + return NS_OK; + } + + if (aContentType == nsIContentPolicy::TYPE_SCRIPT) { + NS_ConvertUTF8toUTF16 typeString(aMimeTypeGuess); + nsContentTypeParser mimeParser(typeString); + + // Reject attempts to load JavaScript scripts with a non-default version. + nsAutoString mimeType, version; + if (NS_SUCCEEDED(mimeParser.GetType(mimeType)) && + nsContentUtils::IsJavascriptMIMEType(mimeType) && + NS_SUCCEEDED(mimeParser.GetParameter("version", version))) { + *aShouldLoad = nsIContentPolicy::REJECT_REQUEST; + + LogMessage(NS_MULTILINE_LITERAL_STRING(VERSIONED_JS_BLOCKED_MESSAGE), + aRequestOrigin, typeString, aContext); + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +AddonContentPolicy::ShouldProcess(uint32_t aContentType, + nsIURI* aContentLocation, + nsIURI* aRequestOrigin, + nsISupports* aRequestingContext, + const nsACString& aMimeTypeGuess, + nsISupports* aExtra, + nsIPrincipal* aRequestPrincipal, + int16_t* aShouldProcess) +{ + MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), + "We should only see external content policy types here."); + + *aShouldProcess = nsIContentPolicy::ACCEPT; + return NS_OK; +} + + +// CSP Validation: + +static const char* allowedSchemes[] = { + "blob", + "filesystem", + nullptr +}; + +static const char* allowedHostSchemes[] = { + "https", + "moz-extension", + nullptr +}; + +/** + * Validates a CSP directive to ensure that it is sufficiently stringent. + * In particular, ensures that: + * + * - No remote sources are allowed other than from https: schemes + * + * - No remote sources specify host wildcards for generic domains + * (*.blogspot.com, *.com, *) + * + * - All remote sources and local extension sources specify a host + * + * - No scheme sources are allowed other than blob:, filesystem:, + * moz-extension:, and https: + * + * - No keyword sources are allowed other than 'none', 'self', 'unsafe-eval', + * and hash sources. + */ +class CSPValidator final : public nsCSPSrcVisitor { + public: + CSPValidator(nsAString& aURL, CSPDirective aDirective, bool aDirectiveRequired = true) : + mURL(aURL), + mDirective(CSP_CSPDirectiveToString(aDirective)), + mFoundSelf(false) + { + // Start with the default error message for a missing directive, since no + // visitors will be called if the directive isn't present. + if (aDirectiveRequired) { + FormatError("csp.error.missing-directive"); + } + } + + // Visitors + + bool visitSchemeSrc(const nsCSPSchemeSrc& src) override + { + nsAutoString scheme; + src.getScheme(scheme); + + if (SchemeInList(scheme, allowedHostSchemes)) { + FormatError("csp.error.missing-host", scheme); + return false; + } + if (!SchemeInList(scheme, allowedSchemes)) { + FormatError("csp.error.illegal-protocol", scheme); + return false; + } + return true; + }; + + bool visitHostSrc(const nsCSPHostSrc& src) override + { + nsAutoString scheme, host; + + src.getScheme(scheme); + src.getHost(host); + + if (scheme.LowerCaseEqualsLiteral("https")) { + if (!HostIsAllowed(host)) { + FormatError("csp.error.illegal-host-wildcard", scheme); + return false; + } + } else if (scheme.LowerCaseEqualsLiteral("moz-extension")) { + // The CSP parser silently converts 'self' keywords to the origin + // URL, so we need to reconstruct the URL to see if it was present. + if (!mFoundSelf) { + nsAutoString url(u"moz-extension://"); + url.Append(host); + + mFoundSelf = url.Equals(mURL); + } + + if (host.IsEmpty() || host.EqualsLiteral("*")) { + FormatError("csp.error.missing-host", scheme); + return false; + } + } else if (!SchemeInList(scheme, allowedSchemes)) { + FormatError("csp.error.illegal-protocol", scheme); + return false; + } + + return true; + }; + + bool visitKeywordSrc(const nsCSPKeywordSrc& src) override + { + switch (src.getKeyword()) { + case CSP_NONE: + case CSP_SELF: + case CSP_UNSAFE_EVAL: + return true; + + default: + NS_ConvertASCIItoUTF16 keyword(CSP_EnumToKeyword(src.getKeyword())); + + FormatError("csp.error.illegal-keyword", keyword); + return false; + } + }; + + bool visitNonceSrc(const nsCSPNonceSrc& src) override + { + FormatError("csp.error.illegal-keyword", NS_LITERAL_STRING("'nonce-*'")); + return false; + }; + + bool visitHashSrc(const nsCSPHashSrc& src) override + { + return true; + }; + + // Accessors + + inline nsAString& GetError() + { + return mError; + }; + + inline bool FoundSelf() + { + return mFoundSelf; + }; + + + // Formatters + + template + inline void FormatError(const char* aName, const T ...aParams) + { + const char16_t* params[] = { mDirective.get(), aParams.get()... }; + FormatErrorParams(aName, params, MOZ_ARRAY_LENGTH(params)); + }; + + private: + // Validators + + bool HostIsAllowed(nsAString& host) + { + if (host.First() == '*') { + if (host.EqualsLiteral("*") || host[1] != '.') { + return false; + } + + host.Cut(0, 2); + + nsCOMPtr tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + + if (!tldService) { + return false; + } + + NS_ConvertUTF16toUTF8 cHost(host); + nsAutoCString publicSuffix; + + nsresult rv = tldService->GetPublicSuffixFromHost(cHost, publicSuffix); + + return NS_SUCCEEDED(rv) && !cHost.Equals(publicSuffix); + } + + return true; + }; + + bool SchemeInList(nsAString& scheme, const char** schemes) + { + for (; *schemes; schemes++) { + if (scheme.LowerCaseEqualsASCII(*schemes)) { + return true; + } + } + return false; + }; + + + // Formatters + + already_AddRefed + GetStringBundle() + { + nsCOMPtr sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, nullptr); + + nsCOMPtr stringBundle; + sbs->CreateBundle("chrome://global/locale/extensions.properties", + getter_AddRefs(stringBundle)); + + return stringBundle.forget(); + }; + + void FormatErrorParams(const char* aName, const char16_t** aParams, int32_t aLength) + { + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr stringBundle = GetStringBundle(); + + if (stringBundle) { + NS_ConvertASCIItoUTF16 name(aName); + + rv = stringBundle->FormatStringFromName(name.get(), aParams, aLength, + getter_Copies(mError)); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mError.AssignLiteral("An unexpected error occurred"); + } + }; + + + // Data members + + nsAutoString mURL; + NS_ConvertASCIItoUTF16 mDirective; + nsXPIDLString mError; + + bool mFoundSelf; +}; + +/** + * Validates a custom content security policy string for use by an add-on. + * In particular, ensures that: + * + * - Both object-src and script-src directives are present, and meet + * the policies required by the CSPValidator class + * + * - The script-src directive includes the source 'self' + */ +NS_IMETHODIMP +AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString, + nsAString& aResult) +{ + nsresult rv; + + // Validate against a randomly-generated extension origin. + // There is no add-on-specific behavior in the CSP code, beyond the ability + // for add-ons to specify a custom policy, but the parser requires a valid + // origin in order to operate correctly. + nsAutoString url(u"moz-extension://"); + { + nsCOMPtr uuidgen = services::GetUUIDGenerator(); + NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE); + + nsID id; + rv = uuidgen->GenerateUUIDInPlace(&id); + NS_ENSURE_SUCCESS(rv, rv); + + char idString[NSID_LENGTH]; + id.ToProvidedString(idString); + + MOZ_RELEASE_ASSERT(idString[0] == '{' && idString[NSID_LENGTH - 2] == '}', + "UUID generator did not return a valid UUID"); + + url.AppendASCII(idString + 1, NSID_LENGTH - 3); + } + + + RefPtr principal = + BasePrincipal::CreateCodebasePrincipal(NS_ConvertUTF16toUTF8(url)); + + nsCOMPtr csp; + rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp)); + NS_ENSURE_SUCCESS(rv, rv); + + + csp->AppendPolicy(aPolicyString, false, false); + + const nsCSPPolicy* policy = csp->GetPolicy(0); + if (!policy) { + CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE); + aResult.Assign(validator.GetError()); + return NS_OK; + } + + bool haveValidDefaultSrc = false; + { + CSPDirective directive = nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; + CSPValidator validator(url, directive); + + haveValidDefaultSrc = policy->visitDirectiveSrcs(directive, &validator); + } + + aResult.SetIsVoid(true); + { + CSPDirective directive = nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE; + CSPValidator validator(url, directive, !haveValidDefaultSrc); + + if (!policy->visitDirectiveSrcs(directive, &validator)) { + aResult.Assign(validator.GetError()); + } else if (!validator.FoundSelf()) { + validator.FormatError("csp.error.missing-source", NS_LITERAL_STRING("'self'")); + aResult.Assign(validator.GetError()); + } + } + + if (aResult.IsVoid()) { + CSPDirective directive = nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE; + CSPValidator validator(url, directive, !haveValidDefaultSrc); + + if (!policy->visitDirectiveSrcs(directive, &validator)) { + aResult.Assign(validator.GetError()); + } + } + + return NS_OK; +} diff --git a/toolkit/mozapps/extensions/AddonContentPolicy.h b/toolkit/mozapps/extensions/AddonContentPolicy.h new file mode 100644 index 000000000..4c8af4828 --- /dev/null +++ b/toolkit/mozapps/extensions/AddonContentPolicy.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIContentPolicy.h" +#include "nsIAddonPolicyService.h" + +class AddonContentPolicy : public nsIContentPolicy, + public nsIAddonContentPolicy +{ +protected: + virtual ~AddonContentPolicy(); + +public: + AddonContentPolicy(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPOLICY + NS_DECL_NSIADDONCONTENTPOLICY +}; diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm new file mode 100644 index 000000000..c5cb80091 --- /dev/null +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -0,0 +1,3674 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as +// most tests later register different nsIAppInfo implementations, which +// wouldn't be reflected in Services.appinfo anymore, as the lazy getter +// underlying it would have been initialized if we used it here. +if ("@mozilla.org/xre/app-info;1" in Cc) { + let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); + if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + // Refuse to run in child processes. + throw new Error("You cannot use the AddonManager in child processes!"); + } +} + +Cu.import("resource://gre/modules/AppConstants.jsm"); + +const MOZ_COMPATIBILITY_NIGHTLY = !['aurora', 'beta', 'release', 'esr'].includes(AppConstants.MOZ_UPDATE_CHANNEL); + +const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; +const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled"; +const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; +const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; +const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion"; +const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; +const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; +const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; +const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; +const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; +const PREF_APP_UPDATE_AUTO = "app.update.auto"; +const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; +const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; +const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; +const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; +const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; +const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; +const PREF_SELECTED_LOCALE = "general.useragent.locale"; +const UNKNOWN_XPCOM_ABI = "unknownABI"; + +const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; +const PREF_WEBAPI_TESTING = "extensions.webapi.testing"; + +const UPDATE_REQUEST_VERSION = 2; +const CATEGORY_UPDATE_PARAMS = "extension-update-params"; + +const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; + +const KEY_PROFILEDIR = "ProfD"; +const KEY_APPDIR = "XCurProcD"; +const FILE_BLOCKLIST = "blocklist.xml"; + +const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; +const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility"; +var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY ? + PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly" : + undefined; + +const TOOLKIT_ID = "toolkit@mozilla.org"; + +const VALID_TYPES_REGEXP = /^[\w\-]+$/; + +const WEBAPI_INSTALL_HOSTS = ["addons.mozilla.org", "testpilot.firefox.com"]; +const WEBAPI_TEST_INSTALL_HOSTS = [ + "addons.allizom.org", "addons-dev.allizom.org", + "testpilot.stage.mozaws.net", "testpilot.dev.mozaws.net", + "example.com", +]; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/AsyncShutdown.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", + "resource://gre/modules/addons/AddonRepository.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Extension", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "CertUtils", function() { + let certUtils = {}; + Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); + return certUtils; +}); + +const INTEGER = /^[1-9]\d*$/; + +this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; + +const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; + +// A list of providers to load by default +const DEFAULT_PROVIDERS = [ + "resource://gre/modules/addons/XPIProvider.jsm", + "resource://gre/modules/LightweightThemeManager.jsm" +]; + +Cu.import("resource://gre/modules/Log.jsm"); +// Configure a logger at the parent 'addons' level to format +// messages for all the modules under addons.* +const PARENT_LOGGER_ID = "addons"; +var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID); +parentLogger.level = Log.Level.Warn; +var formatter = new Log.BasicFormatter(); +// Set parent logger (and its children) to append to +// the Javascript section of the Browser Console +parentLogger.addAppender(new Log.ConsoleAppender(formatter)); +// Set parent logger (and its children) to +// also append to standard out +parentLogger.addAppender(new Log.DumpAppender(formatter)); + +// Create a new logger (child of 'addons' logger) +// for use by the Addons Manager +const LOGGER_ID = "addons.manager"; +var logger = Log.repository.getLogger(LOGGER_ID); + +// Provide the ability to enable/disable logging +// messages at runtime. +// If the "extensions.logging.enabled" preference is +// missing or 'false', messages at the WARNING and higher +// severity should be logged to the JS console and standard error. +// If "extensions.logging.enabled" is set to 'true', messages +// at DEBUG and higher should go to JS console and standard error. +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; + +const UNNAMED_PROVIDER = ""; +function providerName(aProvider) { + return aProvider.name || UNNAMED_PROVIDER; +} + +/** + * Preference listener which listens for a change in the + * "extensions.logging.enabled" preference and changes the logging level of the + * parent 'addons' level logger accordingly. + */ +var PrefObserver = { + init: function() { + Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic == "xpcom-shutdown") { + Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); + Services.obs.removeObserver(this, "xpcom-shutdown"); + } + else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { + let debugLogEnabled = false; + try { + debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); + } + catch (e) { + } + if (debugLogEnabled) { + parentLogger.level = Log.Level.Debug; + } + else { + parentLogger.level = Log.Level.Warn; + } + } + } +}; + +PrefObserver.init(); + +/** + * Calls a callback method consuming any thrown exception. Any parameters after + * the callback parameter will be passed to the callback. + * + * @param aCallback + * The callback method to call + */ +function safeCall(aCallback, ...aArgs) { + try { + aCallback.apply(null, aArgs); + } + catch (e) { + logger.warn("Exception calling callback", e); + } +} + +/** + * Creates a function that will call the passed callback catching and logging + * any exceptions. + * + * @param aCallback + * The callback method to call + */ +function makeSafe(aCallback) { + return function(...aArgs) { + safeCall(aCallback, ...aArgs); + } +} + +/** + * Report an exception thrown by a provider API method. + */ +function reportProviderError(aProvider, aMethod, aError) { + let method = `provider ${providerName(aProvider)}.${aMethod}`; + AddonManagerPrivate.recordException("AMI", method, aError); + logger.error("Exception calling " + method, aError); +} + +/** + * Calls a method on a provider if it exists and consumes any thrown exception. + * Any parameters after the aDefault parameter are passed to the provider's method. + * + * @param aProvider + * The provider to call + * @param aMethod + * The method name to call + * @param aDefault + * A default return value if the provider does not implement the named + * method or throws an error. + * @return the return value from the provider, or aDefault if the provider does not + * implement method or throws an error + */ +function callProvider(aProvider, aMethod, aDefault, ...aArgs) { + if (!(aMethod in aProvider)) + return aDefault; + + try { + return aProvider[aMethod].apply(aProvider, aArgs); + } + catch (e) { + reportProviderError(aProvider, aMethod, e); + return aDefault; + } +} + +/** + * Calls a method on a provider if it exists and consumes any thrown exception. + * Parameters after aMethod are passed to aProvider.aMethod(). + * The last parameter must be a callback function. + * If the provider does not implement the method, or the method throws, calls + * the callback with 'undefined'. + * + * @param aProvider + * The provider to call + * @param aMethod + * The method name to call + */ +function callProviderAsync(aProvider, aMethod, ...aArgs) { + let callback = aArgs[aArgs.length - 1]; + if (!(aMethod in aProvider)) { + callback(undefined); + return undefined; + } + try { + return aProvider[aMethod].apply(aProvider, aArgs); + } + catch (e) { + reportProviderError(aProvider, aMethod, e); + callback(undefined); + return undefined; + } +} + +/** + * Calls a method on a provider if it exists and consumes any thrown exception. + * Parameters after aMethod are passed to aProvider.aMethod() and an additional + * callback is added for the provider to return a result to. + * + * @param aProvider + * The provider to call + * @param aMethod + * The method name to call + * @return {Promise} + * @resolves The result the provider returns, or |undefined| if the provider + * does not implement the method or the method throws. + * @rejects Never + */ +function promiseCallProvider(aProvider, aMethod, ...aArgs) { + return new Promise(resolve => { + callProviderAsync(aProvider, aMethod, ...aArgs, resolve); + }); +} + +/** + * Gets the currently selected locale for display. + * @return the selected locale or "en-US" if none is selected + */ +function getLocale() { + try { + if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE)) + return Services.locale.getLocaleComponentForUserAgent(); + } + catch (e) { } + + try { + let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, + Ci.nsIPrefLocalizedString); + if (locale) + return locale; + } + catch (e) { } + + try { + return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); + } + catch (e) { } + + return "en-US"; +} + +function webAPIForAddon(addon) { + if (!addon) { + return null; + } + + let result = {}; + + // By default just pass through any plain property, the webidl will + // control access. Also filter out private properties, regular Addon + // objects are okay but MockAddon used in tests has non-serializable + // private properties. + for (let prop in addon) { + if (prop[0] != "_" && typeof(addon[prop]) != "function") { + result[prop] = addon[prop]; + } + } + + // A few properties are computed for a nicer API + result.isEnabled = !addon.userDisabled; + result.canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL); + + return result; +} + +/** + * A helper class to repeatedly call a listener with each object in an array + * optionally checking whether the object has a method in it. + * + * @param aObjects + * The array of objects to iterate through + * @param aMethod + * An optional method name, if not null any objects without this method + * will not be passed to the listener + * @param aListener + * A listener implementing nextObject and noMoreObjects methods. The + * former will be called with the AsyncObjectCaller as the first + * parameter and the object as the second. noMoreObjects will be passed + * just the AsyncObjectCaller + */ +function AsyncObjectCaller(aObjects, aMethod, aListener) { + this.objects = [...aObjects]; + this.method = aMethod; + this.listener = aListener; + + this.callNext(); +} + +AsyncObjectCaller.prototype = { + objects: null, + method: null, + listener: null, + + /** + * Passes the next object to the listener or calls noMoreObjects if there + * are none left. + */ + callNext: function() { + if (this.objects.length == 0) { + this.listener.noMoreObjects(this); + return; + } + + let object = this.objects.shift(); + if (!this.method || this.method in object) + this.listener.nextObject(this, object); + else + this.callNext(); + } +}; + +/** + * Listens for a browser changing origin and cancels the installs that were + * started by it. + */ +function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) { + this.browser = aBrowser; + this.principal = aInstallingPrincipal; + this.installs = aInstalls; + this.installCount = aInstalls.length; + + aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); + Services.obs.addObserver(this, "message-manager-close", true); + + for (let install of this.installs) + install.addListener(this); + + this.registered = true; +} + +BrowserListener.prototype = { + browser: null, + installs: null, + installCount: null, + registered: false, + + unregister: function() { + if (!this.registered) + return; + this.registered = false; + + Services.obs.removeObserver(this, "message-manager-close"); + // The browser may have already been detached + if (this.browser.removeProgressListener) + this.browser.removeProgressListener(this); + + for (let install of this.installs) + install.removeListener(this); + this.installs = null; + }, + + cancelInstalls: function() { + for (let install of this.installs) { + try { + install.cancel(); + } + catch (e) { + // Some installs may have already failed or been cancelled, ignore these + } + } + }, + + observe: function(subject, topic, data) { + if (subject != this.browser.messageManager) + return; + + // The browser's message manager has closed and so the browser is + // going away, cancel all installs + this.cancelInstalls(); + }, + + onLocationChange: function(webProgress, request, location) { + if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal)) + return; + + // The browser has navigated to a new origin so cancel all installs + this.cancelInstalls(); + }, + + onDownloadCancelled: function(install) { + // Don't need to hear more events from this install + install.removeListener(this); + + // Once all installs have ended unregister everything + if (--this.installCount == 0) + this.unregister(); + }, + + onDownloadFailed: function(install) { + this.onDownloadCancelled(install); + }, + + onInstallFailed: function(install) { + this.onDownloadCancelled(install); + }, + + onInstallEnded: function(install) { + this.onDownloadCancelled(install); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, + Ci.nsIWebProgressListener, + Ci.nsIObserver]) +}; + +/** + * This represents an author of an add-on (e.g. creator or developer) + * + * @param aName + * The name of the author + * @param aURL + * The URL of the author's profile page + */ +function AddonAuthor(aName, aURL) { + this.name = aName; + this.url = aURL; +} + +AddonAuthor.prototype = { + name: null, + url: null, + + // Returns the author's name, defaulting to the empty string + toString: function() { + return this.name || ""; + } +} + +/** + * This represents an screenshot for an add-on + * + * @param aURL + * The URL to the full version of the screenshot + * @param aWidth + * The width in pixels of the screenshot + * @param aHeight + * The height in pixels of the screenshot + * @param aThumbnailURL + * The URL to the thumbnail version of the screenshot + * @param aThumbnailWidth + * The width in pixels of the thumbnail version of the screenshot + * @param aThumbnailHeight + * The height in pixels of the thumbnail version of the screenshot + * @param aCaption + * The caption of the screenshot + */ +function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL, + aThumbnailWidth, aThumbnailHeight, aCaption) { + this.url = aURL; + if (aWidth) this.width = aWidth; + if (aHeight) this.height = aHeight; + if (aThumbnailURL) this.thumbnailURL = aThumbnailURL; + if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth; + if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight; + if (aCaption) this.caption = aCaption; +} + +AddonScreenshot.prototype = { + url: null, + width: null, + height: null, + thumbnailURL: null, + thumbnailWidth: null, + thumbnailHeight: null, + caption: null, + + // Returns the screenshot URL, defaulting to the empty string + toString: function() { + return this.url || ""; + } +} + + +/** + * This represents a compatibility override for an addon. + * + * @param aType + * Overrride type - "compatible" or "incompatible" + * @param aMinVersion + * Minimum version of the addon to match + * @param aMaxVersion + * Maximum version of the addon to match + * @param aAppID + * Application ID used to match appMinVersion and appMaxVersion + * @param aAppMinVersion + * Minimum version of the application to match + * @param aAppMaxVersion + * Maximum version of the application to match + */ +function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID, + aAppMinVersion, aAppMaxVersion) { + this.type = aType; + this.minVersion = aMinVersion; + this.maxVersion = aMaxVersion; + this.appID = aAppID; + this.appMinVersion = aAppMinVersion; + this.appMaxVersion = aAppMaxVersion; +} + +AddonCompatibilityOverride.prototype = { + /** + * Type of override - "incompatible" or "compatible". + * Only "incompatible" is supported for now. + */ + type: null, + + /** + * Min version of the addon to match. + */ + minVersion: null, + + /** + * Max version of the addon to match. + */ + maxVersion: null, + + /** + * Application ID to match. + */ + appID: null, + + /** + * Min version of the application to match. + */ + appMinVersion: null, + + /** + * Max version of the application to match. + */ + appMaxVersion: null +}; + + +/** + * A type of add-on, used by the UI to determine how to display different types + * of add-ons. + * + * @param aID + * The add-on type ID + * @param aLocaleURI + * The URI of a localized properties file to get the displayable name + * for the type from + * @param aLocaleKey + * The key for the string in the properties file or the actual display + * name if aLocaleURI is null. Include %ID% to include the type ID in + * the key + * @param aViewType + * The optional type of view to use in the UI + * @param aUIPriority + * The priority is used by the UI to list the types in order. Lower + * values push the type higher in the list. + * @param aFlags + * An option set of flags that customize the display of the add-on in + * the UI. + */ +function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) { + if (!aID) + throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG); + + if (aViewType && aUIPriority === undefined) + throw Components.Exception("An AddonType with a defined view must have a set UI priority", + Cr.NS_ERROR_INVALID_ARG); + + if (!aLocaleKey) + throw Components.Exception("An AddonType must have a displayable name", + Cr.NS_ERROR_INVALID_ARG); + + this.id = aID; + this.uiPriority = aUIPriority; + this.viewType = aViewType; + this.flags = aFlags; + + if (aLocaleURI) { + XPCOMUtils.defineLazyGetter(this, "name", () => { + let bundle = Services.strings.createBundle(aLocaleURI); + return bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID)); + }); + } + else { + this.name = aLocaleKey; + } +} + +var gStarted = false; +var gStartupComplete = false; +var gCheckCompatibility = true; +var gStrictCompatibility = true; +var gCheckUpdateSecurityDefault = true; +var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; +var gUpdateEnabled = true; +var gAutoUpdateDefault = true; +var gHotfixID = null; +var gWebExtensionsMinPlatformVersion = null; +var gShutdownBarrier = null; +var gRepoShutdownState = ""; +var gShutdownInProgress = false; +var gPluginPageListener = null; + +/** + * This is the real manager, kept here rather than in AddonManager to keep its + * contents hidden from API users. + */ +var AddonManagerInternal = { + managerListeners: [], + installListeners: [], + addonListeners: [], + typeListeners: [], + pendingProviders: new Set(), + providers: new Set(), + providerShutdowns: new Map(), + types: {}, + startupChanges: {}, + // Store telemetry details per addon provider + telemetryDetails: {}, + upgradeListeners: new Map(), + + recordTimestamp: function(name, value) { + this.TelemetryTimestamps.add(name, value); + }, + + validateBlocklist: function() { + let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); + + // If there is no application shipped blocklist then there is nothing to do + if (!appBlocklist.exists()) + return; + + let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); + + // If there is no blocklist in the profile then copy the application shipped + // one there + if (!profileBlocklist.exists()) { + try { + appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); + } + catch (e) { + logger.warn("Failed to copy the application shipped blocklist to the profile", e); + } + return; + } + + let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + try { + let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Ci.nsIConverterInputStream); + fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); + cstream.init(fileStream, "UTF-8", 0, 0); + + let data = ""; + let str = {}; + let read = 0; + do { + read = cstream.readString(0xffffffff, str); + data += str.value; + } while (read != 0); + + let parser = Cc["@mozilla.org/xmlextras/domparser;1"]. + createInstance(Ci.nsIDOMParser); + var doc = parser.parseFromString(data, "text/xml"); + } + catch (e) { + logger.warn("Application shipped blocklist could not be loaded", e); + return; + } + finally { + try { + fileStream.close(); + } + catch (e) { + logger.warn("Unable to close blocklist file stream", e); + } + } + + // If the namespace is incorrect then ignore the application shipped + // blocklist + if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { + logger.warn("Application shipped blocklist has an unexpected namespace (" + + doc.documentElement.namespaceURI + ")"); + return; + } + + // If there is no lastupdate information then ignore the application shipped + // blocklist + if (!doc.documentElement.hasAttribute("lastupdate")) + return; + + // If the application shipped blocklist is older than the profile blocklist + // then do nothing + if (doc.documentElement.getAttribute("lastupdate") <= + profileBlocklist.lastModifiedTime) + return; + + // Otherwise copy the application shipped blocklist to the profile + try { + appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); + } + catch (e) { + logger.warn("Failed to copy the application shipped blocklist to the profile", e); + } + }, + + /** + * Start up a provider, and register its shutdown hook if it has one + */ + _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + logger.debug(`Starting provider: ${providerName(aProvider)}`); + callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion); + if ('shutdown' in aProvider) { + let name = providerName(aProvider); + let AMProviderShutdown = () => { + // If the provider has been unregistered, it will have been removed from + // this.providers. If it hasn't been unregistered, then this is a normal + // shutdown - and we move it to this.pendingProviders incase we're + // running in a test that will start AddonManager again. + if (this.providers.has(aProvider)) { + this.providers.delete(aProvider); + this.pendingProviders.add(aProvider); + } + + return new Promise((resolve, reject) => { + logger.debug("Calling shutdown blocker for " + name); + resolve(aProvider.shutdown()); + }) + .catch(err => { + logger.warn("Failure during shutdown of " + name, err); + AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err); + }); + }; + logger.debug("Registering shutdown blocker for " + name); + this.providerShutdowns.set(aProvider, AMProviderShutdown); + AddonManager.shutdown.addBlocker(name, AMProviderShutdown); + } + + this.pendingProviders.delete(aProvider); + this.providers.add(aProvider); + logger.debug(`Provider finished startup: ${providerName(aProvider)}`); + }, + + _getProviderByName(aName) { + for (let provider of this.providers) { + if (providerName(provider) == aName) + return provider; + } + return undefined; + }, + + /** + * Initializes the AddonManager, loading any known providers and initializing + * them. + */ + startup: function() { + try { + if (gStarted) + return; + + this.recordTimestamp("AMI_startup_begin"); + + // clear this for xpcshell test restarts + for (let provider in this.telemetryDetails) + delete this.telemetryDetails[provider]; + + let appChanged = undefined; + + let oldAppVersion = null; + try { + oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION); + appChanged = Services.appinfo.version != oldAppVersion; + } + catch (e) { } + + Extension.browserUpdated = appChanged; + + let oldPlatformVersion = null; + try { + oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION); + } + catch (e) { } + + if (appChanged !== false) { + logger.debug("Application has been upgraded"); + Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION, + Services.appinfo.version); + Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION, + Services.appinfo.platformVersion); + Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, + (appChanged === undefined ? 0 : -1)); + this.validateBlocklist(); + } + + if (!MOZ_COMPATIBILITY_NIGHTLY) { + PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." + + Services.appinfo.version.replace(BRANCH_REGEXP, "$1"); + } + + try { + gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false); + + try { + gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false); + + try { + let defaultBranch = Services.prefs.getDefaultBranch(""); + gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); + } catch (e) {} + + try { + gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false); + + try { + gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false); + + try { + gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false); + + try { + gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); + + try { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + } catch (e) {} + Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); + + let defaultProvidersEnabled = true; + try { + defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); + } catch (e) {} + AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled); + + // Ensure all default providers have had a chance to register themselves + if (defaultProvidersEnabled) { + for (let url of DEFAULT_PROVIDERS) { + try { + let scope = {}; + Components.utils.import(url, scope); + // Sanity check - make sure the provider exports a symbol that + // has a 'startup' method + let syms = Object.keys(scope); + if ((syms.length < 1) || + (typeof scope[syms[0]].startup != "function")) { + logger.warn("Provider " + url + " has no startup()"); + AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()"); + } + logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource()); + } + catch (e) { + AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); + logger.error("Exception loading default provider \"" + url + "\"", e); + } + } + } + + // Load any providers registered in the category manager + let catman = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE); + while (entries.hasMoreElements()) { + let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; + let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry); + + try { + Components.utils.import(url, {}); + logger.debug(`Loaded provider scope for ${url}`); + } + catch (e) { + AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); + logger.error("Exception loading provider " + entry + " from category \"" + + url + "\"", e); + } + } + + // Register our shutdown handler with the AsyncShutdown manager + gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down."); + AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.", + this.shutdownManager.bind(this), + {fetchState: this.shutdownState.bind(this)}); + + // Once we start calling providers we must allow all normal methods to work. + gStarted = true; + + for (let provider of this.pendingProviders) { + this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion); + } + + // If this is a new profile just pretend that there were no changes + if (appChanged === undefined) { + for (let type in this.startupChanges) + delete this.startupChanges[type]; + } + + // Support for remote about:plugins. Note that this module isn't loaded + // at the top because Services.appinfo is defined late in tests. + let { RemotePages } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {}); + + gPluginPageListener = new RemotePages("about:plugins"); + gPluginPageListener.addMessageListener("RequestPlugins", this.requestPlugins); + + gStartupComplete = true; + this.recordTimestamp("AMI_startup_end"); + } + catch (e) { + logger.error("startup failed", e); + AddonManagerPrivate.recordException("AMI", "startup failed", e); + } + + logger.debug("Completed startup sequence"); + this.callManagerListeners("onStartup"); + }, + + /** + * Registers a new AddonProvider. + * + * @param aProvider + * The provider to register + * @param aTypes + * An optional array of add-on types + */ + registerProvider: function(aProvider, aTypes) { + if (!aProvider || typeof aProvider != "object") + throw Components.Exception("aProvider must be specified", + Cr.NS_ERROR_INVALID_ARG); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + this.pendingProviders.add(aProvider); + + if (aTypes) { + for (let type of aTypes) { + if (!(type.id in this.types)) { + if (!VALID_TYPES_REGEXP.test(type.id)) { + logger.warn("Ignoring invalid type " + type.id); + return; + } + + this.types[type.id] = { + type: type, + providers: [aProvider] + }; + + let typeListeners = this.typeListeners.slice(0); + for (let listener of typeListeners) + safeCall(() => listener.onTypeAdded(type)); + } + else { + this.types[type.id].providers.push(aProvider); + } + } + } + + // If we're registering after startup call this provider's startup. + if (gStarted) { + this._startProvider(aProvider); + } + }, + + /** + * Unregisters an AddonProvider. + * + * @param aProvider + * The provider to unregister + * @return Whatever the provider's 'shutdown' method returns (if anything). + * For providers that have async shutdown methods returning Promises, + * the caller should wait for that Promise to resolve. + */ + unregisterProvider: function(aProvider) { + if (!aProvider || typeof aProvider != "object") + throw Components.Exception("aProvider must be specified", + Cr.NS_ERROR_INVALID_ARG); + + this.providers.delete(aProvider); + // The test harness will unregister XPIProvider *after* shutdown, which is + // after the provider will have been moved from providers to + // pendingProviders. + this.pendingProviders.delete(aProvider); + + for (let type in this.types) { + this.types[type].providers = this.types[type].providers.filter(p => p != aProvider); + if (this.types[type].providers.length == 0) { + let oldType = this.types[type].type; + delete this.types[type]; + + let typeListeners = this.typeListeners.slice(0); + for (let listener of typeListeners) + safeCall(() => listener.onTypeRemoved(oldType)); + } + } + + // If we're unregistering after startup but before shutting down, + // remove the blocker for this provider's shutdown and call it. + // If we're already shutting down, just let gShutdownBarrier call it to avoid races. + if (gStarted && !gShutdownInProgress) { + logger.debug("Unregistering shutdown blocker for " + providerName(aProvider)); + let shutter = this.providerShutdowns.get(aProvider); + if (shutter) { + this.providerShutdowns.delete(aProvider); + gShutdownBarrier.client.removeBlocker(shutter); + return shutter(); + } + } + return undefined; + }, + + /** + * Mark a provider as safe to access via AddonManager APIs, before its + * startup has completed. + * + * Normally a provider isn't marked as safe until after its (synchronous) + * startup() method has returned. Until a provider has been marked safe, + * it won't be used by any of the AddonManager APIs. markProviderSafe() + * allows a provider to mark itself as safe during its startup; this can be + * useful if the provider wants to perform tasks that block startup, which + * happen after its required initialization tasks and therefore when the + * provider is in a safe state. + * + * @param aProvider Provider object to mark safe + */ + markProviderSafe: function(aProvider) { + if (!gStarted) { + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + } + + if (!aProvider || typeof aProvider != "object") { + throw Components.Exception("aProvider must be specified", + Cr.NS_ERROR_INVALID_ARG); + } + + if (!this.pendingProviders.has(aProvider)) { + return; + } + + this.pendingProviders.delete(aProvider); + this.providers.add(aProvider); + }, + + /** + * Calls a method on all registered providers if it exists and consumes any + * thrown exception. Return values are ignored. Any parameters after the + * method parameter are passed to the provider's method. + * WARNING: Do not use for asynchronous calls; callProviders() does not + * invoke callbacks if provider methods throw synchronous exceptions. + * + * @param aMethod + * The method name to call + * @see callProvider + */ + callProviders: function(aMethod, ...aArgs) { + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + try { + if (aMethod in provider) + provider[aMethod].apply(provider, aArgs); + } + catch (e) { + reportProviderError(provider, aMethod, e); + } + } + }, + + /** + * Report the current state of asynchronous shutdown + */ + shutdownState() { + let state = []; + if (gShutdownBarrier) { + state.push({ + name: gShutdownBarrier.client.name, + state: gShutdownBarrier.state + }); + } + state.push({ + name: "AddonRepository: async shutdown", + state: gRepoShutdownState + }); + return state; + }, + + /** + * Shuts down the addon manager and all registered providers, this must clean + * up everything in order for automated tests to fake restarts. + * @return Promise{null} that resolves when all providers and dependent modules + * have finished shutting down + */ + shutdownManager: Task.async(function*() { + logger.debug("shutdown"); + this.callManagerListeners("onShutdown"); + + gRepoShutdownState = "pending"; + gShutdownInProgress = true; + // Clean up listeners + Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this); + Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this); + Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this); + Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this); + Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this); + Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this); + gPluginPageListener.destroy(); + gPluginPageListener = null; + + let savedError = null; + // Only shut down providers if they've been started. + if (gStarted) { + try { + yield gShutdownBarrier.wait(); + } + catch (err) { + savedError = err; + logger.error("Failure during wait for shutdown barrier", err); + AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err); + } + } + + // Shut down AddonRepository after providers (if any). + try { + gRepoShutdownState = "in progress"; + yield AddonRepository.shutdown(); + gRepoShutdownState = "done"; + } + catch (err) { + savedError = err; + logger.error("Failure during AddonRepository shutdown", err); + AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err); + } + + logger.debug("Async provider shutdown done"); + this.managerListeners.splice(0, this.managerListeners.length); + this.installListeners.splice(0, this.installListeners.length); + this.addonListeners.splice(0, this.addonListeners.length); + this.typeListeners.splice(0, this.typeListeners.length); + this.providerShutdowns.clear(); + for (let type in this.startupChanges) + delete this.startupChanges[type]; + gStarted = false; + gStartupComplete = false; + gShutdownBarrier = null; + gShutdownInProgress = false; + if (savedError) { + throw savedError; + } + }), + + requestPlugins: function({ target: port }) { + // Lists all the properties that plugins.html needs + const NEEDED_PROPS = ["name", "pluginLibraries", "pluginFullpath", "version", + "isActive", "blocklistState", "description", + "pluginMimeTypes"]; + function filterProperties(plugin) { + let filtered = {}; + for (let prop of NEEDED_PROPS) { + filtered[prop] = plugin[prop]; + } + return filtered; + } + + AddonManager.getAddonsByTypes(["plugin"], function(aPlugins) { + port.sendAsyncMessage("PluginList", aPlugins.map(filterProperties)); + }); + }, + + /** + * Notified when a preference we're interested in has changed. + * + * @see nsIObserver + */ + observe: function(aSubject, aTopic, aData) { + switch (aData) { + case PREF_EM_CHECK_COMPATIBILITY: { + let oldValue = gCheckCompatibility; + try { + gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); + } catch (e) { + gCheckCompatibility = true; + } + + this.callManagerListeners("onCompatibilityModeChanged"); + + if (gCheckCompatibility != oldValue) + this.updateAddonAppDisabledStates(); + + break; + } + case PREF_EM_STRICT_COMPATIBILITY: { + let oldValue = gStrictCompatibility; + try { + gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); + } catch (e) { + gStrictCompatibility = true; + } + + this.callManagerListeners("onCompatibilityModeChanged"); + + if (gStrictCompatibility != oldValue) + this.updateAddonAppDisabledStates(); + + break; + } + case PREF_EM_CHECK_UPDATE_SECURITY: { + let oldValue = gCheckUpdateSecurity; + try { + gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); + } catch (e) { + gCheckUpdateSecurity = true; + } + + this.callManagerListeners("onCheckUpdateSecurityChanged"); + + if (gCheckUpdateSecurity != oldValue) + this.updateAddonAppDisabledStates(); + + break; + } + case PREF_EM_UPDATE_ENABLED: { + let oldValue = gUpdateEnabled; + try { + gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); + } catch (e) { + gUpdateEnabled = true; + } + + this.callManagerListeners("onUpdateModeChanged"); + break; + } + case PREF_EM_AUTOUPDATE_DEFAULT: { + let oldValue = gAutoUpdateDefault; + try { + gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); + } catch (e) { + gAutoUpdateDefault = true; + } + + this.callManagerListeners("onUpdateModeChanged"); + break; + } + case PREF_EM_HOTFIX_ID: { + try { + gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); + } catch (e) { + gHotfixID = null; + } + break; + } + case PREF_MIN_WEBEXT_PLATFORM_VERSION: { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + break; + } + } + }, + + /** + * Replaces %...% strings in an addon url (update and updateInfo) with + * appropriate values. + * + * @param aAddon + * The Addon representing the add-on + * @param aUri + * The string representation of the URI to escape + * @param aAppVersion + * The optional application version to use for %APP_VERSION% + * @return The appropriately escaped URI. + */ + escapeAddonURI: function(aAddon, aUri, aAppVersion) + { + if (!aAddon || typeof aAddon != "object") + throw Components.Exception("aAddon must be an Addon object", + Cr.NS_ERROR_INVALID_ARG); + + if (!aUri || typeof aUri != "string") + throw Components.Exception("aUri must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aAppVersion && typeof aAppVersion != "string") + throw Components.Exception("aAppVersion must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled" + : "userEnabled"; + + if (!aAddon.isCompatible) + addonStatus += ",incompatible"; + if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) + addonStatus += ",blocklisted"; + if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) + addonStatus += ",softblocked"; + + try { + var xpcomABI = Services.appinfo.XPCOMABI; + } catch (ex) { + xpcomABI = UNKNOWN_XPCOM_ABI; + } + + let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id); + uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version); + uri = uri.replace(/%ITEM_STATUS%/g, addonStatus); + uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID); + uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : + Services.appinfo.version); + uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION); + uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS); + uri = uri.replace(/%APP_ABI%/g, xpcomABI); + uri = uri.replace(/%APP_LOCALE%/g, getLocale()); + uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version); + + // Replace custom parameters (names of custom parameters must have at + // least 3 characters to prevent lookups for something like %D0%C8) + var catMan = null; + uri = uri.replace(/%(\w{3,})%/g, function(aMatch, aParam) { + if (!catMan) { + catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + } + + try { + var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam); + var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2); + return paramHandler.getPropertyAsAString(aParam); + } + catch (e) { + return aMatch; + } + }); + + // escape() does not properly encode + symbols in any embedded FVF strings. + return uri.replace(/\+/g, "%2B"); + }, + + /** + * Performs a background update check by starting an update for all add-ons + * that can be updated. + * @return Promise{null} Resolves when the background update check is complete + * (the resulting addon installations may still be in progress). + */ + backgroundUpdateCheck: function() { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + let buPromise = Task.spawn(function*() { + let hotfixID = this.hotfixID; + + let appUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && + Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); + let checkHotfix = hotfixID && appUpdateEnabled; + + logger.debug("Background update check beginning"); + + Services.obs.notifyObservers(null, "addons-background-update-start", null); + + if (this.updateEnabled) { + let scope = {}; + Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope); + scope.LightweightThemeManager.updateCurrentTheme(); + + let allAddons = yield new Promise((resolve, reject) => this.getAllAddons(resolve)); + + // Repopulate repository cache first, to ensure compatibility overrides + // are up to date before checking for addon updates. + yield AddonRepository.backgroundUpdateCheck(); + + // Keep track of all the async add-on updates happening in parallel + let updates = []; + + for (let addon of allAddons) { + if (addon.id == hotfixID) { + continue; + } + + // Check all add-ons for updates so that any compatibility updates will + // be applied + updates.push(new Promise((resolve, reject) => { + addon.findUpdates({ + onUpdateAvailable: function(aAddon, aInstall) { + // Start installing updates when the add-on can be updated and + // background updates should be applied. + logger.debug("Found update for add-on ${id}", aAddon); + if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && + AddonManager.shouldAutoUpdate(aAddon)) { + // XXX we really should resolve when this install is done, + // not when update-available check completes, no? + logger.debug(`Starting upgrade install of ${aAddon.id}`); + aInstall.install(); + } + }, + + onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + })); + } + yield Promise.all(updates); + } + + if (checkHotfix) { + var hotfixVersion = ""; + try { + hotfixVersion = Services.prefs.getCharPref(PREF_EM_HOTFIX_LASTVERSION); + } + catch (e) { } + + let url = null; + if (Services.prefs.getPrefType(PREF_EM_HOTFIX_URL) == Ci.nsIPrefBranch.PREF_STRING) + url = Services.prefs.getCharPref(PREF_EM_HOTFIX_URL); + else + url = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL); + + // Build the URI from a fake add-on data. + url = AddonManager.escapeAddonURI({ + id: hotfixID, + version: hotfixVersion, + userDisabled: false, + appDisabled: false + }, url); + + Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); + let update = null; + try { + let foundUpdates = yield new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(hotfixID, null, url, { + onUpdateCheckComplete: resolve, + onUpdateCheckError: reject + }); + }); + update = AddonUpdateChecker.getNewestCompatibleUpdate(foundUpdates); + } catch (e) { + // AUC.checkForUpdates already logged the error + } + + // Check that we have a hotfix update, and it's newer than the one we already + // have installed (if any) + if (update) { + if (Services.vc.compare(hotfixVersion, update.version) < 0) { + logger.debug("Downloading hotfix version " + update.version); + let aInstall = yield new Promise((resolve, reject) => + AddonManager.getInstallForURL(update.updateURL, resolve, + "application/x-xpinstall", update.updateHash, null, + null, update.version)); + + aInstall.addListener({ + onDownloadEnded: function(aInstall) { + if (aInstall.addon.id != hotfixID) { + logger.warn("The downloaded hotfix add-on did not have the " + + "expected ID and so will not be installed."); + aInstall.cancel(); + return; + } + + // If XPIProvider has reported the hotfix as properly signed then + // there is nothing more to do here + if (aInstall.addon.signedState == AddonManager.SIGNEDSTATE_SIGNED) + return; + + try { + if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES)) + return; + } + catch (e) { + // By default don't do certificate checks. + return; + } + + try { + CertUtils.validateCert(aInstall.certificate, + CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS)); + } + catch (e) { + logger.warn("The hotfix add-on was not signed by the expected " + + "certificate and so will not be installed.", e); + aInstall.cancel(); + } + }, + + onInstallEnded: function(aInstall) { + // Remember the last successfully installed version. + Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, + aInstall.version); + }, + + onInstallCancelled: function(aInstall) { + // Revert to the previous version if the installation was + // cancelled. + Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, + hotfixVersion); + } + }); + + aInstall.install(); + } + } + } + + if (appUpdateEnabled) { + try { + yield AddonManagerInternal._getProviderByName("XPIProvider").updateSystemAddons(); + } + catch (e) { + logger.warn("Failed to update system addons", e); + } + } + + logger.debug("Background update check complete"); + Services.obs.notifyObservers(null, + "addons-background-update-complete", + null); + }.bind(this)); + // Fork the promise chain so we can log the error and let our caller see it too. + buPromise.then(null, e => logger.warn("Error in background update", e)); + return buPromise; + }, + + /** + * Adds a add-on to the list of detected changes for this startup. If + * addStartupChange is called multiple times for the same add-on in the same + * startup then only the most recent change will be remembered. + * + * @param aType + * The type of change as a string. Providers can define their own + * types of changes or use the existing defined STARTUP_CHANGE_* + * constants + * @param aID + * The ID of the add-on + */ + addStartupChange: function(aType, aID) { + if (!aType || typeof aType != "string") + throw Components.Exception("aType must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (!aID || typeof aID != "string") + throw Components.Exception("aID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (gStartupComplete) + return; + logger.debug("Registering startup change '" + aType + "' for " + aID); + + // Ensure that an ID is only listed in one type of change + for (let type in this.startupChanges) + this.removeStartupChange(type, aID); + + if (!(aType in this.startupChanges)) + this.startupChanges[aType] = []; + this.startupChanges[aType].push(aID); + }, + + /** + * Removes a startup change for an add-on. + * + * @param aType + * The type of change + * @param aID + * The ID of the add-on + */ + removeStartupChange: function(aType, aID) { + if (!aType || typeof aType != "string") + throw Components.Exception("aType must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (!aID || typeof aID != "string") + throw Components.Exception("aID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (gStartupComplete) + return; + + if (!(aType in this.startupChanges)) + return; + + this.startupChanges[aType] = this.startupChanges[aType].filter(aItem => aItem != aID); + }, + + /** + * Calls all registered AddonManagerListeners with an event. Any parameters + * after the method parameter are passed to the listener. + * + * @param aMethod + * The method on the listeners to call + */ + callManagerListeners: function(aMethod, ...aArgs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let managerListeners = this.managerListeners.slice(0); + for (let listener of managerListeners) { + try { + if (aMethod in listener) + listener[aMethod].apply(listener, aArgs); + } + catch (e) { + logger.warn("AddonManagerListener threw exception when calling " + aMethod, e); + } + } + }, + + /** + * Calls all registered InstallListeners with an event. Any parameters after + * the extraListeners parameter are passed to the listener. + * + * @param aMethod + * The method on the listeners to call + * @param aExtraListeners + * An optional array of extra InstallListeners to also call + * @return false if any of the listeners returned false, true otherwise + */ + callInstallListeners: function(aMethod, + aExtraListeners, ...aArgs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aExtraListeners && !Array.isArray(aExtraListeners)) + throw Components.Exception("aExtraListeners must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + let result = true; + let listeners; + if (aExtraListeners) + listeners = aExtraListeners.concat(this.installListeners); + else + listeners = this.installListeners.slice(0); + + for (let listener of listeners) { + try { + if (aMethod in listener) { + if (listener[aMethod].apply(listener, aArgs) === false) + result = false; + } + } + catch (e) { + logger.warn("InstallListener threw exception when calling " + aMethod, e); + } + } + return result; + }, + + /** + * Calls all registered AddonListeners with an event. Any parameters after + * the method parameter are passed to the listener. + * + * @param aMethod + * The method on the listeners to call + */ + callAddonListeners: function(aMethod, ...aArgs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let addonListeners = this.addonListeners.slice(0); + for (let listener of addonListeners) { + try { + if (aMethod in listener) + listener[aMethod].apply(listener, aArgs); + } + catch (e) { + logger.warn("AddonListener threw exception when calling " + aMethod, e); + } + } + }, + + /** + * Notifies all providers that an add-on has been enabled when that type of + * add-on only supports a single add-on being enabled at a time. This allows + * the providers to disable theirs if necessary. + * + * @param aID + * The ID of the enabled add-on + * @param aType + * The type of the enabled add-on + * @param aPendingRestart + * A boolean indicating if the change will only take place the next + * time the application is restarted + */ + notifyAddonChanged: function(aID, aType, aPendingRestart) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aID && typeof aID != "string") + throw Components.Exception("aID must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (!aType || typeof aType != "string") + throw Components.Exception("aType must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + // Temporary hack until bug 520124 lands. + // We can get here during synchronous startup, at which point it's + // considered unsafe (and therefore disallowed by AddonManager.jsm) to + // access providers that haven't been initialized yet. Since this is when + // XPIProvider is starting up, XPIProvider can't access itself via APIs + // going through AddonManager.jsm. Furthermore, LightweightThemeManager may + // not be initialized until after XPIProvider is, and therefore would also + // be unaccessible during XPIProvider startup. Thankfully, these are the + // only two uses of this API, and we know it's safe to use this API with + // both providers; so we have this hack to allow bypassing the normal + // safetey guard. + // The notifyAddonChanged/addonChanged API will be unneeded and therefore + // removed by bug 520124, so this is a temporary quick'n'dirty hack. + let providers = [...this.providers, ...this.pendingProviders]; + for (let provider of providers) { + callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart); + } + }, + + /** + * Notifies all providers they need to update the appDisabled property for + * their add-ons in response to an application change such as a blocklist + * update. + */ + updateAddonAppDisabledStates: function() { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + this.callProviders("updateAddonAppDisabledStates"); + }, + + /** + * Notifies all providers that the repository has updated its data for + * installed add-ons. + * + * @param aCallback + * Function to call when operation is complete. + */ + updateAddonRepositoryData: function(aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "updateAddonRepositoryData", + aCaller.callNext.bind(aCaller)); + }, + noMoreObjects: function(aCaller) { + safeCall(aCallback); + // only tests should care about this + Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null); + } + }); + }, + + /** + * Asynchronously gets an AddonInstall for a URL. + * + * @param aUrl + * The string represenation of the URL the add-on is located at + * @param aCallback + * A callback to pass the AddonInstall to + * @param aMimetype + * The mimetype of the add-on + * @param aHash + * An optional hash of the add-on + * @param aName + * An optional placeholder name while the add-on is being downloaded + * @param aIcons + * Optional placeholder icons while the add-on is being downloaded + * @param aVersion + * An optional placeholder version while the add-on is being downloaded + * @param aLoadGroup + * An optional nsILoadGroup to associate any network requests with + * @throws if the aUrl, aCallback or aMimetype arguments are not specified + */ + getInstallForURL: function(aUrl, aCallback, aMimetype, + aHash, aName, aIcons, + aVersion, aBrowser) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aUrl || typeof aUrl != "string") + throw Components.Exception("aURL must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aHash && typeof aHash != "string") + throw Components.Exception("aHash must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (aName && typeof aName != "string") + throw Components.Exception("aName must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (aIcons) { + if (typeof aIcons == "string") + aIcons = { "32": aIcons }; + else if (typeof aIcons != "object") + throw Components.Exception("aIcons must be a string, an object or null", + Cr.NS_ERROR_INVALID_ARG); + } else { + aIcons = {}; + } + + if (aVersion && typeof aVersion != "string") + throw Components.Exception("aVersion must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (aBrowser && (!(aBrowser instanceof Ci.nsIDOMElement))) + throw Components.Exception("aBrowser must be a nsIDOMElement or null", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + if (callProvider(provider, "supportsMimetype", false, aMimetype)) { + callProviderAsync(provider, "getInstallForURL", + aUrl, aHash, aName, aIcons, aVersion, aBrowser, + function getInstallForURL_safeCall(aInstall) { + safeCall(aCallback, aInstall); + }); + return; + } + } + safeCall(aCallback, null); + }, + + /** + * Asynchronously gets an AddonInstall for an nsIFile. + * + * @param aFile + * The nsIFile where the add-on is located + * @param aCallback + * A callback to pass the AddonInstall to + * @param aMimetype + * An optional mimetype hint for the add-on + * @throws if the aFile or aCallback arguments are not specified + */ + getInstallForFile: function(aFile, aCallback, aMimetype) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!(aFile instanceof Ci.nsIFile)) + throw Components.Exception("aFile must be a nsIFile", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + if (aMimetype && typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + new AsyncObjectCaller(this.providers, "getInstallForFile", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getInstallForFile", aFile, + function(aInstall) { + if (aInstall) + safeCall(aCallback, aInstall); + else + aCaller.callNext(); + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, null); + } + }); + }, + + /** + * Asynchronously gets all current AddonInstalls optionally limiting to a list + * of types. + * + * @param aTypes + * An optional array of types to retrieve. Each type is a string name + * @param aCallback + * A callback which will be passed an array of AddonInstalls + * @throws If the aCallback argument is not specified + */ + getInstallsByTypes: function(aTypes, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + let installs = []; + + new AsyncObjectCaller(this.providers, "getInstallsByTypes", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getInstallsByTypes", aTypes, + function(aProviderInstalls) { + if (aProviderInstalls) { + installs = installs.concat(aProviderInstalls); + } + aCaller.callNext(); + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, installs); + } + }); + }, + + /** + * Asynchronously gets all current AddonInstalls. + * + * @param aCallback + * A callback which will be passed an array of AddonInstalls + */ + getAllInstalls: function(aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + this.getInstallsByTypes(null, aCallback); + }, + + /** + * Synchronously map a URI to the corresponding Addon ID. + * + * Mappable URIs are limited to in-application resources belonging to the + * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. + * but do not include URIs from meta data, such as the add-on homepage. + * + * @param aURI + * nsIURI to map to an addon id + * @return string containing the Addon ID or null + * @see amIAddonManager.mapURIToAddonID + */ + mapURIToAddonID: function(aURI) { + if (!(aURI instanceof Ci.nsIURI)) { + throw Components.Exception("aURI is not a nsIURI", + Cr.NS_ERROR_INVALID_ARG); + } + + // Try all providers + let providers = [...this.providers]; + for (let provider of providers) { + var id = callProvider(provider, "mapURIToAddonID", null, aURI); + if (id !== null) { + return id; + } + } + + return null; + }, + + /** + * Checks whether installation is enabled for a particular mimetype. + * + * @param aMimetype + * The mimetype to check + * @return true if installation is enabled for the mimetype + */ + isInstallEnabled: function(aMimetype) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + if (callProvider(provider, "supportsMimetype", false, aMimetype) && + callProvider(provider, "isInstallEnabled")) + return true; + } + return false; + }, + + /** + * Checks whether a particular source is allowed to install add-ons of a + * given mimetype. + * + * @param aMimetype + * The mimetype of the add-on + * @param aInstallingPrincipal + * The nsIPrincipal that initiated the install + * @return true if the source is allowed to install this mimetype + */ + isInstallAllowed: function(aMimetype, aInstallingPrincipal) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) + throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + if (callProvider(provider, "supportsMimetype", false, aMimetype) && + callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal)) + return true; + } + return false; + }, + + /** + * Starts installation of an array of AddonInstalls notifying the registered + * web install listener of blocked or started installs. + * + * @param aMimetype + * The mimetype of add-ons being installed + * @param aBrowser + * The optional browser element that started the installs + * @param aInstallingPrincipal + * The nsIPrincipal that initiated the install + * @param aInstalls + * The array of AddonInstalls to be installed + */ + installAddonsFromWebpage: function(aMimetype, aBrowser, + aInstallingPrincipal, aInstalls) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement)) + throw Components.Exception("aSource must be a nsIDOMElement, or null", + Cr.NS_ERROR_INVALID_ARG); + + if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) + throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", + Cr.NS_ERROR_INVALID_ARG); + + if (!Array.isArray(aInstalls)) + throw Components.Exception("aInstalls must be an array", + Cr.NS_ERROR_INVALID_ARG); + + if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) { + logger.warn("No web installer available, cancelling all installs"); + for (let install of aInstalls) + install.cancel(); + return; + } + + // When a chrome in-content UI has loaded a inside to host a + // website we want to do our security checks on the inner-browser but + // notify front-end that install events came from the outer-browser (the + // main tab's browser). Check this by seeing if the browser we've been + // passed is in a content type docshell and if so get the outer-browser. + let topBrowser = aBrowser; + let docShell = aBrowser.ownerDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIDocShellTreeItem); + if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent) + topBrowser = docShell.chromeEventHandler; + + try { + let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]. + getService(Ci.amIWebInstallListener); + + if (!this.isInstallEnabled(aMimetype)) { + for (let install of aInstalls) + install.cancel(); + + weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length); + return; + } + else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) { + for (let install of aInstalls) + install.cancel(); + + if (weblistener instanceof Ci.amIWebInstallListener2) { + weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length); + } + return; + } + + // The installs may start now depending on the web install listener, + // listen for the browser navigating to a new origin and cancel the + // installs in that case. + new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls); + + if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) { + if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length)) { + for (let install of aInstalls) + install.install(); + } + } + else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length)) { + for (let install of aInstalls) + install.install(); + } + } + catch (e) { + // In the event that the weblistener throws during instantiation or when + // calling onWebInstallBlocked or onWebInstallRequested all of the + // installs should get cancelled. + logger.warn("Failure calling web installer", e); + for (let install of aInstalls) + install.cancel(); + } + }, + + /** + * Adds a new InstallListener if the listener is not already registered. + * + * @param aListener + * The InstallListener to add + */ + addInstallListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a InstallListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.installListeners.some(function(i) { + return i == aListener; })) + this.installListeners.push(aListener); + }, + + /** + * Removes an InstallListener if the listener is registered. + * + * @param aListener + * The InstallListener to remove + */ + removeInstallListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a InstallListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.installListeners.length) { + if (this.installListeners[pos] == aListener) + this.installListeners.splice(pos, 1); + else + pos++; + } + }, + /* + * Adds new or overrides existing UpgradeListener. + * + * @param aInstanceID + * The instance ID of an addon to register a listener for. + * @param aCallback + * The callback to invoke when updates are available for this addon. + * @throws if there is no addon matching the instanceID + */ + addUpgradeListener: function(aInstanceID, aCallback) { + if (!aInstanceID || typeof aInstanceID != "symbol") + throw Components.Exception("aInstanceID must be a symbol", + Cr.NS_ERROR_INVALID_ARG); + + if (!aCallback || typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + this.getAddonByInstanceID(aInstanceID).then(wrapper => { + if (!wrapper) { + throw Error("No addon matching instanceID:", aInstanceID.toString()); + } + let addonId = wrapper.addonId(); + logger.debug(`Registering upgrade listener for ${addonId}`); + this.upgradeListeners.set(addonId, aCallback); + }); + }, + + /** + * Removes an UpgradeListener if the listener is registered. + * + * @param aInstanceID + * The instance ID of the addon to remove + */ + removeUpgradeListener: function(aInstanceID) { + if (!aInstanceID || typeof aInstanceID != "symbol") + throw Components.Exception("aInstanceID must be a symbol", + Cr.NS_ERROR_INVALID_ARG); + + this.getAddonByInstanceID(aInstanceID).then(addon => { + if (!addon) { + throw Error("No addon for instanceID:", aInstanceID.toString()); + } + if (this.upgradeListeners.has(addon.id)) { + this.upgradeListeners.delete(addon.id); + } else { + throw Error("No upgrade listener registered for addon ID:", addon.id); + } + }); + }, + + /** + * Installs a temporary add-on from a local file or directory. + * @param aFile + * An nsIFile for the file or directory of the add-on to be + * temporarily installed. + * @return a Promise that rejects if the add-on is not a valid restartless + * add-on or if the same ID is already temporarily installed. + */ + installTemporaryAddon: function(aFile) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!(aFile instanceof Ci.nsIFile)) + throw Components.Exception("aFile must be a nsIFile", + Cr.NS_ERROR_INVALID_ARG); + + return AddonManagerInternal._getProviderByName("XPIProvider") + .installTemporaryAddon(aFile); + }, + + installAddonFromSources: function(aFile) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!(aFile instanceof Ci.nsIFile)) + throw Components.Exception("aFile must be a nsIFile", + Cr.NS_ERROR_INVALID_ARG); + + return AddonManagerInternal._getProviderByName("XPIProvider") + .installAddonFromSources(aFile); + }, + + /** + * Returns an Addon corresponding to an instance ID. + * @param aInstanceID + * An Addon Instance ID symbol + * @return {Promise} + * @resolves The found Addon or null if no such add-on exists. + * @rejects Never + * @throws if the aInstanceID argument is not specified + * or the AddonManager is not initialized + */ + getAddonByInstanceID: function(aInstanceID) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aInstanceID || typeof aInstanceID != "symbol") + throw Components.Exception("aInstanceID must be a Symbol()", + Cr.NS_ERROR_INVALID_ARG); + + return AddonManagerInternal._getProviderByName("XPIProvider") + .getAddonByInstanceID(aInstanceID); + }, + + /** + * Gets an icon from the icon set provided by the add-on + * that is closest to the specified size. + * + * The optional window parameter will be used to determine + * the screen resolution and select a more appropriate icon. + * Calling this method with 48px on retina screens will try to + * match an icon of size 96px. + * + * @param aAddon + * An addon object, meaning: + * An object with either an icons property that is a key-value + * list of icon size and icon URL, or an object having an iconURL + * and icon64URL property. + * @param aSize + * Ideal icon size in pixels + * @param aWindow + * Optional window object for determining the correct scale. + * @return {String} The absolute URL of the icon or null if the addon doesn't have icons + */ + getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { + if (aWindow && aWindow.devicePixelRatio) { + aSize *= aWindow.devicePixelRatio; + } + + let icons = aAddon.icons; + + // certain addon-types only have iconURLs + if (!icons) { + icons = {}; + if (aAddon.iconURL) { + icons[32] = aAddon.iconURL; + icons[48] = aAddon.iconURL; + } + if (aAddon.icon64URL) { + icons[64] = aAddon.icon64URL; + } + } + + // quick return if the exact size was found + if (icons[aSize]) { + return icons[aSize]; + } + + let bestSize = null; + + for (let size of Object.keys(icons)) { + if (!INTEGER.test(size)) { + throw Components.Exception("Invalid icon size, must be an integer", + Cr.NS_ERROR_ILLEGAL_VALUE); + } + + size = parseInt(size, 10); + + if (!bestSize) { + bestSize = size; + continue; + } + + if (size > aSize && bestSize > aSize) { + // If both best size and current size are larger than the wanted size then choose + // the one closest to the wanted size + bestSize = Math.min(bestSize, size); + } + else { + // Otherwise choose the largest of the two so we'll prefer sizes as close to below aSize + // or above aSize + bestSize = Math.max(bestSize, size); + } + } + + return icons[bestSize] || null; + }, + + /** + * Asynchronously gets an add-on with a specific ID. + * + * @param aID + * The ID of the add-on to retrieve + * @return {Promise} + * @resolves The found Addon or null if no such add-on exists. + * @rejects Never + * @throws if the aID argument is not specified + */ + getAddonByID: function(aID) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aID || typeof aID != "string") + throw Components.Exception("aID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let promises = Array.from(this.providers, + p => promiseCallProvider(p, "getAddonByID", aID)); + return Promise.all(promises).then(aAddons => { + return aAddons.find(a => !!a) || null; + }); + }, + + /** + * Asynchronously get an add-on with a specific Sync GUID. + * + * @param aGUID + * String GUID of add-on to retrieve + * @param aCallback + * The callback to pass the retrieved add-on to. + * @throws if the aGUID or aCallback arguments are not specified + */ + getAddonBySyncGUID: function(aGUID, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aGUID || typeof aGUID != "string") + throw Components.Exception("aGUID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID, + function(aAddon) { + if (aAddon) { + safeCall(aCallback, aAddon); + } else { + aCaller.callNext(); + } + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, null); + } + }); + }, + + /** + * Asynchronously gets an array of add-ons. + * + * @param aIDs + * The array of IDs to retrieve + * @return {Promise} + * @resolves The array of found add-ons. + * @rejects Never + * @throws if the aIDs argument is not specified + */ + getAddonsByIDs: function(aIDs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!Array.isArray(aIDs)) + throw Components.Exception("aIDs must be an array", + Cr.NS_ERROR_INVALID_ARG); + + let promises = aIDs.map(a => AddonManagerInternal.getAddonByID(a)); + return Promise.all(promises); + }, + + /** + * Asynchronously gets add-ons of specific types. + * + * @param aTypes + * An optional array of types to retrieve. Each type is a string name + * @param aCallback + * The callback to pass an array of Addons to. + * @throws if the aCallback argument is not specified + */ + getAddonsByTypes: function(aTypes, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + let addons = []; + + new AsyncObjectCaller(this.providers, "getAddonsByTypes", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getAddonsByTypes", aTypes, + function(aProviderAddons) { + if (aProviderAddons) { + addons = addons.concat(aProviderAddons); + } + aCaller.callNext(); + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, addons); + } + }); + }, + + /** + * Asynchronously gets all installed add-ons. + * + * @param aCallback + * A callback which will be passed an array of Addons + */ + getAllAddons: function(aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + this.getAddonsByTypes(null, aCallback); + }, + + /** + * Asynchronously gets add-ons that have operations waiting for an application + * restart to complete. + * + * @param aTypes + * An optional array of types to retrieve. Each type is a string name + * @param aCallback + * The callback to pass the array of Addons to + * @throws if the aCallback argument is not specified + */ + getAddonsWithOperationsByTypes: function(aTypes, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + let addons = []; + + new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", { + nextObject: function getAddonsWithOperationsByTypes_nextObject + (aCaller, aProvider) { + callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes, + function getAddonsWithOperationsByTypes_concatAddons + (aProviderAddons) { + if (aProviderAddons) { + addons = addons.concat(aProviderAddons); + } + aCaller.callNext(); + }); + }, + + noMoreObjects: function(caller) { + safeCall(aCallback, addons); + } + }); + }, + + /** + * Adds a new AddonManagerListener if the listener is not already registered. + * + * @param aListener + * The listener to add + */ + addManagerListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonManagerListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.managerListeners.some(i => i == aListener)) + this.managerListeners.push(aListener); + }, + + /** + * Removes an AddonManagerListener if the listener is registered. + * + * @param aListener + * The listener to remove + */ + removeManagerListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonManagerListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.managerListeners.length) { + if (this.managerListeners[pos] == aListener) + this.managerListeners.splice(pos, 1); + else + pos++; + } + }, + + /** + * Adds a new AddonListener if the listener is not already registered. + * + * @param aListener + * The AddonListener to add + */ + addAddonListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.addonListeners.some(i => i == aListener)) + this.addonListeners.push(aListener); + }, + + /** + * Removes an AddonListener if the listener is registered. + * + * @param aListener + * The AddonListener to remove + */ + removeAddonListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.addonListeners.length) { + if (this.addonListeners[pos] == aListener) + this.addonListeners.splice(pos, 1); + else + pos++; + } + }, + + /** + * Adds a new TypeListener if the listener is not already registered. + * + * @param aListener + * The TypeListener to add + */ + addTypeListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a TypeListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.typeListeners.some(i => i == aListener)) + this.typeListeners.push(aListener); + }, + + /** + * Removes an TypeListener if the listener is registered. + * + * @param aListener + * The TypeListener to remove + */ + removeTypeListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a TypeListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.typeListeners.length) { + if (this.typeListeners[pos] == aListener) + this.typeListeners.splice(pos, 1); + else + pos++; + } + }, + + get addonTypes() { + // A read-only wrapper around the types dictionary + return new Proxy(this.types, { + defineProperty(target, property, descriptor) { + // Not allowed to define properties + return false; + }, + + deleteProperty(target, property) { + // Not allowed to delete properties + return false; + }, + + get(target, property, receiver) { + if (!target.hasOwnProperty(property)) + return undefined; + + return target[property].type; + }, + + getOwnPropertyDescriptor(target, property) { + if (!target.hasOwnProperty(property)) + return undefined; + + return { + value: target[property].type, + writable: false, + // Claim configurability to maintain the proxy invariants. + configurable: true, + enumerable: true + } + }, + + preventExtensions(target) { + // Not allowed to prevent adding new properties + return false; + }, + + set(target, property, value, receiver) { + // Not allowed to set properties + return false; + }, + + setPrototypeOf(target, prototype) { + // Not allowed to change prototype + return false; + } + }); + }, + + get autoUpdateDefault() { + return gAutoUpdateDefault; + }, + + set autoUpdateDefault(aValue) { + aValue = !!aValue; + if (aValue != gAutoUpdateDefault) + Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue); + return aValue; + }, + + get checkCompatibility() { + return gCheckCompatibility; + }, + + set checkCompatibility(aValue) { + aValue = !!aValue; + if (aValue != gCheckCompatibility) { + if (!aValue) + Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false); + else + Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY); + } + return aValue; + }, + + get strictCompatibility() { + return gStrictCompatibility; + }, + + set strictCompatibility(aValue) { + aValue = !!aValue; + if (aValue != gStrictCompatibility) + Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue); + return aValue; + }, + + get checkUpdateSecurityDefault() { + return gCheckUpdateSecurityDefault; + }, + + get checkUpdateSecurity() { + return gCheckUpdateSecurity; + }, + + set checkUpdateSecurity(aValue) { + aValue = !!aValue; + if (aValue != gCheckUpdateSecurity) { + if (aValue != gCheckUpdateSecurityDefault) + Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue); + else + Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); + } + return aValue; + }, + + get updateEnabled() { + return gUpdateEnabled; + }, + + set updateEnabled(aValue) { + aValue = !!aValue; + if (aValue != gUpdateEnabled) + Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue); + return aValue; + }, + + get hotfixID() { + return gHotfixID; + }, + + webAPI: { + // installs maps integer ids to AddonInstall instances. + installs: new Map(), + nextInstall: 0, + + sendEvent: null, + setEventHandler(fn) { + this.sendEvent = fn; + }, + + getAddonByID(target, id) { + return new Promise(resolve => { + AddonManager.getAddonByID(id, (addon) => { + resolve(webAPIForAddon(addon)); + }); + }); + }, + + // helper to copy (and convert) the properties we care about + copyProps(install, obj) { + obj.state = AddonManager.stateToString(install.state); + obj.error = AddonManager.errorToString(install.error); + obj.progress = install.progress; + obj.maxProgress = install.maxProgress; + }, + + makeListener(id, mm) { + const events = [ + "onDownloadStarted", + "onDownloadProgress", + "onDownloadEnded", + "onDownloadCancelled", + "onDownloadFailed", + "onInstallStarted", + "onInstallEnded", + "onInstallCancelled", + "onInstallFailed", + ]; + + let listener = {}; + events.forEach(event => { + listener[event] = (install) => { + let data = {event, id}; + AddonManager.webAPI.copyProps(install, data); + this.sendEvent(mm, data); + } + }); + return listener; + }, + + forgetInstall(id) { + let info = this.installs.get(id); + if (!info) { + throw new Error(`forgetInstall cannot find ${id}`); + } + info.install.removeListener(info.listener); + this.installs.delete(id); + }, + + createInstall(target, options) { + // Throw an appropriate error if the given URL is not valid + // as an installation source. Return silently if it is okay. + function checkInstallUrl(url) { + let host = Services.io.newURI(options.url, null, null).host; + if (WEBAPI_INSTALL_HOSTS.includes(host)) { + return; + } + if (Services.prefs.getBoolPref(PREF_WEBAPI_TESTING) + && WEBAPI_TEST_INSTALL_HOSTS.includes(host)) { + return; + } + + throw new Error(`Install from ${host} not permitted`); + } + + return new Promise((resolve, reject) => { + try { + checkInstallUrl(options.url); + } catch (err) { + reject({message: err.message}); + return; + } + + let newInstall = install => { + let id = this.nextInstall++; + let listener = this.makeListener(id, target.messageManager); + install.addListener(listener); + + this.installs.set(id, {install, target, listener}); + + let result = {id}; + this.copyProps(install, result); + resolve(result); + }; + AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall", options.hash); + }); + }, + + addonUninstall(target, id) { + return new Promise(resolve => { + AddonManager.getAddonByID(id, addon => { + if (!addon) { + resolve(false); + } + + try { + addon.uninstall(); + resolve(true); + } catch (err) { + Cu.reportError(err); + resolve(false); + } + }); + }); + }, + + addonSetEnabled(target, id, value) { + return new Promise((resolve, reject) => { + AddonManager.getAddonByID(id, addon => { + if (!addon) { + reject({message: `No such addon ${id}`}); + } + addon.userDisabled = !value; + resolve(); + }); + }); + }, + + addonInstallDoInstall(target, id) { + let state = this.installs.get(id); + if (!state) { + return Promise.reject(`invalid id ${id}`); + } + return Promise.resolve(state.install.install()); + }, + + addonInstallCancel(target, id) { + let state = this.installs.get(id); + if (!state) { + return Promise.reject(`invalid id ${id}`); + } + return Promise.resolve(state.install.cancel()); + }, + + clearInstalls(ids) { + for (let id of ids) { + this.forgetInstall(id); + } + }, + + clearInstallsFrom(mm) { + for (let [id, info] of this.installs) { + if (info.target.messageManager == mm) { + this.forgetInstall(id); + } + } + }, + }, +}; + +/** + * Should not be used outside of core Mozilla code. This is a private API for + * the startup and platform integration code to use. Refer to the methods on + * AddonManagerInternal for documentation however note that these methods are + * subject to change at any time. + */ +this.AddonManagerPrivate = { + startup: function() { + AddonManagerInternal.startup(); + }, + + registerProvider: function(aProvider, aTypes) { + AddonManagerInternal.registerProvider(aProvider, aTypes); + }, + + unregisterProvider: function(aProvider) { + AddonManagerInternal.unregisterProvider(aProvider); + }, + + markProviderSafe: function(aProvider) { + AddonManagerInternal.markProviderSafe(aProvider); + }, + + backgroundUpdateCheck: function() { + return AddonManagerInternal.backgroundUpdateCheck(); + }, + + backgroundUpdateTimerHandler() { + // Don't call through to the real update check if no checks are enabled. + let checkHotfix = AddonManagerInternal.hotfixID && + Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && + Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); + + if (!AddonManagerInternal.updateEnabled && !checkHotfix) { + logger.info("Skipping background update check"); + return; + } + // Don't return the promise here, since the caller doesn't care. + AddonManagerInternal.backgroundUpdateCheck(); + }, + + addStartupChange: function(aType, aID) { + AddonManagerInternal.addStartupChange(aType, aID); + }, + + removeStartupChange: function(aType, aID) { + AddonManagerInternal.removeStartupChange(aType, aID); + }, + + notifyAddonChanged: function(aID, aType, aPendingRestart) { + AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart); + }, + + updateAddonAppDisabledStates: function() { + AddonManagerInternal.updateAddonAppDisabledStates(); + }, + + updateAddonRepositoryData: function(aCallback) { + AddonManagerInternal.updateAddonRepositoryData(aCallback); + }, + + callInstallListeners: function(...aArgs) { + return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal, + aArgs); + }, + + callAddonListeners: function(...aArgs) { + AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs); + }, + + AddonAuthor: AddonAuthor, + + AddonScreenshot: AddonScreenshot, + + AddonCompatibilityOverride: AddonCompatibilityOverride, + + AddonType: AddonType, + + recordTimestamp: function(name, value) { + AddonManagerInternal.recordTimestamp(name, value); + }, + + _simpleMeasures: {}, + recordSimpleMeasure: function(name, value) { + this._simpleMeasures[name] = value; + }, + + recordException: function(aModule, aContext, aException) { + let report = { + module: aModule, + context: aContext + }; + + if (typeof aException == "number") { + report.message = Components.Exception("", aException).name; + } + else { + report.message = aException.toString(); + if (aException.fileName) { + report.file = aException.fileName; + report.line = aException.lineNumber; + } + } + + this._simpleMeasures.exception = report; + }, + + getSimpleMeasures: function() { + return this._simpleMeasures; + }, + + getTelemetryDetails: function() { + return AddonManagerInternal.telemetryDetails; + }, + + setTelemetryDetails: function(aProvider, aDetails) { + AddonManagerInternal.telemetryDetails[aProvider] = aDetails; + }, + + // Start a timer, record a simple measure of the time interval when + // timer.done() is called + simpleTimer: function(aName) { + let startTime = Cu.now(); + return { + done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime)) + }; + }, + + /** + * Helper to call update listeners when no update is available. + * + * This can be used as an implementation for Addon.findUpdates() when + * no update mechanism is available. + */ + callNoUpdateListeners: function(addon, listener, reason, appVersion, platformVersion) { + if ("onNoCompatibilityUpdateAvailable" in listener) { + safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon); + } + if ("onNoUpdateAvailable" in listener) { + safeCall(listener.onNoUpdateAvailable.bind(listener), addon); + } + if ("onUpdateFinished" in listener) { + safeCall(listener.onUpdateFinished.bind(listener), addon); + } + }, + + get webExtensionsMinPlatformVersion() { + return gWebExtensionsMinPlatformVersion; + }, + + hasUpgradeListener: function(aId) { + return AddonManagerInternal.upgradeListeners.has(aId); + }, + + getUpgradeListener: function(aId) { + return AddonManagerInternal.upgradeListeners.get(aId); + }, +}; + +/** + * This is the public API that UI and developers should be calling. All methods + * just forward to AddonManagerInternal. + */ +this.AddonManager = { + // Constants for the AddonInstall.state property + // These will show up as AddonManager.STATE_* (eg, STATE_AVAILABLE) + _states: new Map([ + // The install is available for download. + ["STATE_AVAILABLE", 0], + // The install is being downloaded. + ["STATE_DOWNLOADING", 1], + // The install is checking for compatibility information. + ["STATE_CHECKING", 2], + // The install is downloaded and ready to install. + ["STATE_DOWNLOADED", 3], + // The download failed. + ["STATE_DOWNLOAD_FAILED", 4], + // The install has been postponed. + ["STATE_POSTPONED", 5], + // The add-on is being installed. + ["STATE_INSTALLING", 6], + // The add-on has been installed. + ["STATE_INSTALLED", 7], + // The install failed. + ["STATE_INSTALL_FAILED", 8], + // The install has been cancelled. + ["STATE_CANCELLED", 9], + ]), + + // Constants representing different types of errors while downloading an + // add-on. + // These will show up as AddonManager.ERROR_* (eg, ERROR_NETWORK_FAILURE) + _errors: new Map([ + // The download failed due to network problems. + ["ERROR_NETWORK_FAILURE", -1], + // The downloaded file did not match the provided hash. + ["ERROR_INCORRECT_HASH", -2], + // The downloaded file seems to be corrupted in some way. + ["ERROR_CORRUPT_FILE", -3], + // An error occured trying to write to the filesystem. + ["ERROR_FILE_ACCESS", -4], + // The add-on must be signed and isn't. + ["ERROR_SIGNEDSTATE_REQUIRED", -5], + // The downloaded add-on had a different type than expected. + ["ERROR_UNEXPECTED_ADDON_TYPE", -6], + // The addon did not have the expected ID + ["ERROR_INCORRECT_ID", -7], + ]), + + // These must be kept in sync with AddonUpdateChecker. + // No error was encountered. + UPDATE_STATUS_NO_ERROR: 0, + // The update check timed out + UPDATE_STATUS_TIMEOUT: -1, + // There was an error while downloading the update information. + UPDATE_STATUS_DOWNLOAD_ERROR: -2, + // The update information was malformed in some way. + UPDATE_STATUS_PARSE_ERROR: -3, + // The update information was not in any known format. + UPDATE_STATUS_UNKNOWN_FORMAT: -4, + // The update information was not correctly signed or there was an SSL error. + UPDATE_STATUS_SECURITY_ERROR: -5, + // The update was cancelled. + UPDATE_STATUS_CANCELLED: -6, + + // Constants to indicate why an update check is being performed + // Update check has been requested by the user. + UPDATE_WHEN_USER_REQUESTED: 1, + // Update check is necessary to see if the Addon is compatibile with a new + // version of the application. + UPDATE_WHEN_NEW_APP_DETECTED: 2, + // Update check is necessary because a new application has been installed. + UPDATE_WHEN_NEW_APP_INSTALLED: 3, + // Update check is a regular background update check. + UPDATE_WHEN_PERIODIC_UPDATE: 16, + // Update check is needed to check an Addon that is being installed. + UPDATE_WHEN_ADDON_INSTALLED: 17, + + // Constants for operations in Addon.pendingOperations + // Indicates that the Addon has no pending operations. + PENDING_NONE: 0, + // Indicates that the Addon will be enabled after the application restarts. + PENDING_ENABLE: 1, + // Indicates that the Addon will be disabled after the application restarts. + PENDING_DISABLE: 2, + // Indicates that the Addon will be uninstalled after the application restarts. + PENDING_UNINSTALL: 4, + // Indicates that the Addon will be installed after the application restarts. + PENDING_INSTALL: 8, + PENDING_UPGRADE: 16, + + // Constants for operations in Addon.operationsRequiringRestart + // Indicates that restart isn't required for any operation. + OP_NEEDS_RESTART_NONE: 0, + // Indicates that restart is required for enabling the addon. + OP_NEEDS_RESTART_ENABLE: 1, + // Indicates that restart is required for disabling the addon. + OP_NEEDS_RESTART_DISABLE: 2, + // Indicates that restart is required for uninstalling the addon. + OP_NEEDS_RESTART_UNINSTALL: 4, + // Indicates that restart is required for installing the addon. + OP_NEEDS_RESTART_INSTALL: 8, + + // Constants for permissions in Addon.permissions. + // Indicates that the Addon can be uninstalled. + PERM_CAN_UNINSTALL: 1, + // Indicates that the Addon can be enabled by the user. + PERM_CAN_ENABLE: 2, + // Indicates that the Addon can be disabled by the user. + PERM_CAN_DISABLE: 4, + // Indicates that the Addon can be upgraded. + PERM_CAN_UPGRADE: 8, + // Indicates that the Addon can be set to be optionally enabled + // on a case-by-case basis. + PERM_CAN_ASK_TO_ACTIVATE: 16, + + // General descriptions of where items are installed. + // Installed in this profile. + SCOPE_PROFILE: 1, + // Installed for all of this user's profiles. + SCOPE_USER: 2, + // Installed and owned by the application. + SCOPE_APPLICATION: 4, + // Installed for all users of the computer. + SCOPE_SYSTEM: 8, + // Installed temporarily + SCOPE_TEMPORARY: 16, + // The combination of all scopes. + SCOPE_ALL: 31, + + // Add-on type is expected to be displayed in the UI in a list. + VIEW_TYPE_LIST: "list", + + // Constants describing how add-on types behave. + + // If no add-ons of a type are installed, then the category for that add-on + // type should be hidden in the UI. + TYPE_UI_HIDE_EMPTY: 16, + // Indicates that this add-on type supports the ask-to-activate state. + // That is, add-ons of this type can be set to be optionally enabled + // on a case-by-case basis. + TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32, + // The add-on type natively supports undo for restartless uninstalls. + // If this flag is not specified, the UI is expected to handle this via + // disabling the add-on, and performing the actual uninstall at a later time. + TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64, + + // Constants for Addon.applyBackgroundUpdates. + // Indicates that the Addon should not update automatically. + AUTOUPDATE_DISABLE: 0, + // Indicates that the Addon should update automatically only if + // that's the global default. + AUTOUPDATE_DEFAULT: 1, + // Indicates that the Addon should update automatically. + AUTOUPDATE_ENABLE: 2, + + // Constants for how Addon options should be shown. + // Options will be opened in a new window + OPTIONS_TYPE_DIALOG: 1, + // Options will be displayed within the AM detail view + OPTIONS_TYPE_INLINE: 2, + // Options will be displayed in a new tab, if possible + OPTIONS_TYPE_TAB: 3, + // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown. + // Used to indicate that only non-interactive information will be shown. + OPTIONS_TYPE_INLINE_INFO: 4, + // Similar to OPTIONS_TYPE_INLINE, but rather than generating inline + // options from a specially-formatted XUL file, the contents of the + // file are simply displayed in an inline element. + OPTIONS_TYPE_INLINE_BROWSER: 5, + + // Constants for displayed or hidden options notifications + // Options notification will be displayed + OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed", + // Options notification will be hidden + OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden", + + // Constants for getStartupChanges, addStartupChange and removeStartupChange + // Add-ons that were detected as installed during startup. Doesn't include + // add-ons that were pending installation the last time the application ran. + STARTUP_CHANGE_INSTALLED: "installed", + // Add-ons that were detected as changed during startup. This includes an + // add-on moving to a different location, changing version or just having + // been detected as possibly changed. + STARTUP_CHANGE_CHANGED: "changed", + // Add-ons that were detected as uninstalled during startup. Doesn't include + // add-ons that were pending uninstallation the last time the application ran. + STARTUP_CHANGE_UNINSTALLED: "uninstalled", + // Add-ons that were detected as disabled during startup, normally because of + // an application change making an add-on incompatible. Doesn't include + // add-ons that were pending being disabled the last time the application ran. + STARTUP_CHANGE_DISABLED: "disabled", + // Add-ons that were detected as enabled during startup, normally because of + // an application change making an add-on compatible. Doesn't include + // add-ons that were pending being enabled the last time the application ran. + STARTUP_CHANGE_ENABLED: "enabled", + + // Constants for Addon.signedState. Any states that should cause an add-on + // to be unusable in builds that require signing should have negative values. + // Add-on signing is not required, e.g. because the pref is disabled. + SIGNEDSTATE_NOT_REQUIRED: undefined, + // Add-on is signed but signature verification has failed. + SIGNEDSTATE_BROKEN: -2, + // Add-on may be signed but by an certificate that doesn't chain to our + // our trusted certificate. + SIGNEDSTATE_UNKNOWN: -1, + // Add-on is unsigned. + SIGNEDSTATE_MISSING: 0, + // Add-on is preliminarily reviewed. + SIGNEDSTATE_PRELIMINARY: 1, + // Add-on is fully reviewed. + SIGNEDSTATE_SIGNED: 2, + // Add-on is system add-on. + SIGNEDSTATE_SYSTEM: 3, + + // Constants for the Addon.userDisabled property + // Indicates that the userDisabled state of this add-on is currently + // ask-to-activate. That is, it can be conditionally enabled on a + // case-by-case basis. + STATE_ASK_TO_ACTIVATE: "askToActivate", + + get __AddonManagerInternal__() { + return AppConstants.DEBUG ? AddonManagerInternal : undefined; + }, + + get isReady() { + return gStartupComplete && !gShutdownInProgress; + }, + + init() { + this._stateToString = new Map(); + for (let [name, value] of this._states) { + this[name] = value; + this._stateToString.set(value, name); + } + this._errorToString = new Map(); + for (let [name, value] of this._errors) { + this[name] = value; + this._errorToString.set(value, name); + } + }, + + stateToString(state) { + return this._stateToString.get(state); + }, + + errorToString(err) { + return err ? this._errorToString.get(err) : null; + }, + + getInstallForURL: function(aUrl, aCallback, aMimetype, + aHash, aName, aIcons, + aVersion, aBrowser) { + AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash, + aName, aIcons, aVersion, aBrowser); + }, + + getInstallForFile: function(aFile, aCallback, aMimetype) { + AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype); + }, + + /** + * Gets an array of add-on IDs that changed during the most recent startup. + * + * @param aType + * The type of startup change to get + * @return An array of add-on IDs + */ + getStartupChanges: function(aType) { + if (!(aType in AddonManagerInternal.startupChanges)) + return []; + return AddonManagerInternal.startupChanges[aType].slice(0); + }, + + getAddonByID: function(aID, aCallback) { + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + AddonManagerInternal.getAddonByID(aID) + .then(makeSafe(aCallback)) + .catch(logger.error); + }, + + getAddonBySyncGUID: function(aGUID, aCallback) { + AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback); + }, + + getAddonsByIDs: function(aIDs, aCallback) { + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + AddonManagerInternal.getAddonsByIDs(aIDs) + .then(makeSafe(aCallback)) + .catch(logger.error); + }, + + getAddonsWithOperationsByTypes: function(aTypes, aCallback) { + AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback); + }, + + getAddonsByTypes: function(aTypes, aCallback) { + AddonManagerInternal.getAddonsByTypes(aTypes, aCallback); + }, + + getAllAddons: function(aCallback) { + AddonManagerInternal.getAllAddons(aCallback); + }, + + getInstallsByTypes: function(aTypes, aCallback) { + AddonManagerInternal.getInstallsByTypes(aTypes, aCallback); + }, + + getAllInstalls: function(aCallback) { + AddonManagerInternal.getAllInstalls(aCallback); + }, + + mapURIToAddonID: function(aURI) { + return AddonManagerInternal.mapURIToAddonID(aURI); + }, + + isInstallEnabled: function(aType) { + return AddonManagerInternal.isInstallEnabled(aType); + }, + + isInstallAllowed: function(aType, aInstallingPrincipal) { + return AddonManagerInternal.isInstallAllowed(aType, aInstallingPrincipal); + }, + + installAddonsFromWebpage: function(aType, aBrowser, aInstallingPrincipal, + aInstalls) { + AddonManagerInternal.installAddonsFromWebpage(aType, aBrowser, + aInstallingPrincipal, + aInstalls); + }, + + installTemporaryAddon: function(aDirectory) { + return AddonManagerInternal.installTemporaryAddon(aDirectory); + }, + + installAddonFromSources: function(aDirectory) { + return AddonManagerInternal.installAddonFromSources(aDirectory); + }, + + getAddonByInstanceID: function(aInstanceID) { + return AddonManagerInternal.getAddonByInstanceID(aInstanceID); + }, + + addManagerListener: function(aListener) { + AddonManagerInternal.addManagerListener(aListener); + }, + + removeManagerListener: function(aListener) { + AddonManagerInternal.removeManagerListener(aListener); + }, + + addInstallListener: function(aListener) { + AddonManagerInternal.addInstallListener(aListener); + }, + + removeInstallListener: function(aListener) { + AddonManagerInternal.removeInstallListener(aListener); + }, + + getUpgradeListener: function(aId) { + return AddonManagerInternal.upgradeListeners.get(aId); + }, + + addUpgradeListener: function(aInstanceID, aCallback) { + AddonManagerInternal.addUpgradeListener(aInstanceID, aCallback); + }, + + removeUpgradeListener: function(aInstanceID) { + AddonManagerInternal.removeUpgradeListener(aInstanceID); + }, + + addAddonListener: function(aListener) { + AddonManagerInternal.addAddonListener(aListener); + }, + + removeAddonListener: function(aListener) { + AddonManagerInternal.removeAddonListener(aListener); + }, + + addTypeListener: function(aListener) { + AddonManagerInternal.addTypeListener(aListener); + }, + + removeTypeListener: function(aListener) { + AddonManagerInternal.removeTypeListener(aListener); + }, + + get addonTypes() { + return AddonManagerInternal.addonTypes; + }, + + /** + * Determines whether an Addon should auto-update or not. + * + * @param aAddon + * The Addon representing the add-on + * @return true if the addon should auto-update, false otherwise. + */ + shouldAutoUpdate: function(aAddon) { + if (!aAddon || typeof aAddon != "object") + throw Components.Exception("aAddon must be specified", + Cr.NS_ERROR_INVALID_ARG); + + if (!("applyBackgroundUpdates" in aAddon)) + return false; + if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE) + return true; + if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE) + return false; + return this.autoUpdateDefault; + }, + + get checkCompatibility() { + return AddonManagerInternal.checkCompatibility; + }, + + set checkCompatibility(aValue) { + AddonManagerInternal.checkCompatibility = aValue; + }, + + get strictCompatibility() { + return AddonManagerInternal.strictCompatibility; + }, + + set strictCompatibility(aValue) { + AddonManagerInternal.strictCompatibility = aValue; + }, + + get checkUpdateSecurityDefault() { + return AddonManagerInternal.checkUpdateSecurityDefault; + }, + + get checkUpdateSecurity() { + return AddonManagerInternal.checkUpdateSecurity; + }, + + set checkUpdateSecurity(aValue) { + AddonManagerInternal.checkUpdateSecurity = aValue; + }, + + get updateEnabled() { + return AddonManagerInternal.updateEnabled; + }, + + set updateEnabled(aValue) { + AddonManagerInternal.updateEnabled = aValue; + }, + + get autoUpdateDefault() { + return AddonManagerInternal.autoUpdateDefault; + }, + + set autoUpdateDefault(aValue) { + AddonManagerInternal.autoUpdateDefault = aValue; + }, + + get hotfixID() { + return AddonManagerInternal.hotfixID; + }, + + escapeAddonURI: function(aAddon, aUri, aAppVersion) { + return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion); + }, + + getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { + return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow); + }, + + get webAPI() { + return AddonManagerInternal.webAPI; + }, + + get shutdown() { + return gShutdownBarrier.client; + }, +}; + +this.AddonManager.init(); + +// load the timestamps module into AddonManagerInternal +Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal); +Object.freeze(AddonManagerInternal); +Object.freeze(AddonManagerPrivate); +Object.freeze(AddonManager); diff --git a/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp b/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp new file mode 100644 index 000000000..3f2a7a529 --- /dev/null +++ b/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonManagerWebAPI.h" + +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/NavigatorBinding.h" + +#include "mozilla/Preferences.h" +#include "nsGlobalWindow.h" + +#include "nsIDocShell.h" +#include "nsIScriptObjectPrincipal.h" + +namespace mozilla { +using namespace mozilla::dom; + +static bool +IsValidHost(const nsACString& host) { + // This is ugly, but Preferences.h doesn't have support + // for default prefs or locked prefs + nsCOMPtr prefService (do_GetService(NS_PREFSERVICE_CONTRACTID)); + nsCOMPtr prefs; + if (prefService) { + prefService->GetDefaultBranch(nullptr, getter_AddRefs(prefs)); + bool isEnabled; + if (NS_SUCCEEDED(prefs->GetBoolPref("xpinstall.enabled", &isEnabled)) && !isEnabled) { + bool isLocked; + prefs->PrefIsLocked("xpinstall.enabled", &isLocked); + if (isLocked) { + return false; + } + } + } + + if (host.Equals("addons.mozilla.org") || + host.Equals("discovery.addons.mozilla.org") || + host.Equals("testpilot.firefox.com")) { + return true; + } + + // When testing allow access to the developer sites. + if (Preferences::GetBool("extensions.webapi.testing", false)) { + if (host.LowerCaseEqualsLiteral("addons.allizom.org") || + host.LowerCaseEqualsLiteral("discovery.addons.allizom.org") || + host.LowerCaseEqualsLiteral("addons-dev.allizom.org") || + host.LowerCaseEqualsLiteral("discovery.addons-dev.allizom.org") || + host.LowerCaseEqualsLiteral("testpilot.stage.mozaws.net") || + host.LowerCaseEqualsLiteral("testpilot.dev.mozaws.net") || + host.LowerCaseEqualsLiteral("example.com")) { + return true; + } + } + + return false; +} + +// Checks if the given uri is secure and matches one of the hosts allowed to +// access the API. +bool +AddonManagerWebAPI::IsValidSite(nsIURI* uri) +{ + if (!uri) { + return false; + } + + bool isSecure; + nsresult rv = uri->SchemeIs("https", &isSecure); + if (NS_FAILED(rv) || !isSecure) { + return false; + } + + nsAutoCString host; + rv = uri->GetHost(host); + if (NS_FAILED(rv)) { + return false; + } + + return IsValidHost(host); +} + +bool +AddonManagerWebAPI::IsAPIEnabled(JSContext* cx, JSObject* obj) +{ + nsGlobalWindow* global = xpc::WindowGlobalOrNull(obj); + if (!global) { + return false; + } + + nsCOMPtr win = global->AsInner(); + if (!win) { + return false; + } + + // Check that the current window and all parent frames are allowed access to + // the API. + while (win) { + nsCOMPtr sop = do_QueryInterface(win); + if (!sop) { + return false; + } + + nsCOMPtr principal = sop->GetPrincipal(); + if (!principal) { + return false; + } + + // Reaching a window with a system principal means we have reached + // privileged UI of some kind so stop at this point and allow access. + if (principal->GetIsSystemPrincipal()) { + return true; + } + + nsCOMPtr docShell = win->GetDocShell(); + if (!docShell) { + // This window has been torn down so don't allow access to the API. + return false; + } + + if (!IsValidSite(win->GetDocumentURI())) { + return false; + } + + // Checks whether there is a parent frame of the same type. This won't cross + // mozbrowser or chrome boundaries. + nsCOMPtr parent; + nsresult rv = docShell->GetSameTypeParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) { + return false; + } + + if (!parent) { + // No parent means we've hit a mozbrowser or chrome boundary so allow + // access to the API. + return true; + } + + nsIDocument* doc = win->GetDoc(); + if (!doc) { + return false; + } + + doc = doc->GetParentDocument(); + if (!doc) { + // Getting here means something has been torn down so fail safe. + return false; + } + + + win = doc->GetInnerWindow(); + } + + // Found a document with no inner window, don't grant access to the API. + return false; +} + +namespace dom { + +bool +AddonManagerPermissions::IsHostPermitted(const GlobalObject& /*unused*/, const nsAString& host) +{ + return IsValidHost(NS_ConvertUTF16toUTF8(host)); +} + +} // namespace mozilla::dom + + +} // namespace mozilla diff --git a/toolkit/mozapps/extensions/AddonManagerWebAPI.h b/toolkit/mozapps/extensions/AddonManagerWebAPI.h new file mode 100644 index 000000000..6830bc91f --- /dev/null +++ b/toolkit/mozapps/extensions/AddonManagerWebAPI.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef addonmanagerwebapi_h_ +#define addonmanagerwebapi_h_ + +#include "nsPIDOMWindow.h" + +namespace mozilla { + +class AddonManagerWebAPI { +public: + static bool IsAPIEnabled(JSContext* cx, JSObject* obj); + +private: + static bool IsValidSite(nsIURI* uri); +}; + +namespace dom { + +class AddonManagerPermissions { +public: + static bool IsHostPermitted(const GlobalObject&, const nsAString& host); +}; + +} // namespace mozilla::dom + +} // namespace mozilla + +#endif // addonmanagerwebapi_h_ diff --git a/toolkit/mozapps/extensions/AddonPathService.cpp b/toolkit/mozapps/extensions/AddonPathService.cpp new file mode 100644 index 000000000..8a405c0ea --- /dev/null +++ b/toolkit/mozapps/extensions/AddonPathService.cpp @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonPathService.h" + +#include "amIAddonManager.h" +#include "nsIURI.h" +#include "nsXULAppAPI.h" +#include "jsapi.h" +#include "nsServiceManagerUtils.h" +#include "nsLiteralString.h" +#include "nsThreadUtils.h" +#include "nsIIOService.h" +#include "nsNetUtil.h" +#include "nsIAddonPolicyService.h" +#include "nsIFileURL.h" +#include "nsIResProtocolHandler.h" +#include "nsIChromeRegistry.h" +#include "nsIJARURI.h" +#include "nsJSUtils.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/AddonPathService.h" +#include "mozilla/Omnijar.h" + +#include + +namespace mozilla { + +struct PathEntryComparator +{ + typedef AddonPathService::PathEntry PathEntry; + + bool Equals(const PathEntry& entry1, const PathEntry& entry2) const + { + return entry1.mPath == entry2.mPath; + } + + bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const + { + return entry1.mPath < entry2.mPath; + } +}; + +AddonPathService::AddonPathService() +{ +} + +AddonPathService::~AddonPathService() +{ + sInstance = nullptr; +} + +NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService) + +AddonPathService *AddonPathService::sInstance; + +/* static */ AddonPathService* +AddonPathService::GetInstance() +{ + if (!sInstance) { + sInstance = new AddonPathService(); + } + NS_ADDREF(sInstance); + return sInstance; +} + +static JSAddonId* +ConvertAddonId(const nsAString& addonIdString) +{ + AutoSafeJSContext cx; + JS::RootedValue strv(cx); + if (!mozilla::dom::ToJSValue(cx, addonIdString, &strv)) { + return nullptr; + } + JS::RootedString str(cx, strv.toString()); + return JS::NewAddonId(cx, str); +} + +JSAddonId* +AddonPathService::Find(const nsAString& path) +{ + // Use binary search to find the nearest entry that is <= |path|. + PathEntryComparator comparator; + unsigned index = mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator); + if (index == 0) { + return nullptr; + } + const PathEntry& entry = mPaths[index - 1]; + + // Return the entry's addon if its path is a prefix of |path|. + if (StringBeginsWith(path, entry.mPath)) { + return entry.mAddonId; + } + return nullptr; +} + +NS_IMETHODIMP +AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString) +{ + if (JSAddonId* id = Find(path)) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); + AssignJSFlatString(addonIdString, flat); + } + return NS_OK; +} + +/* static */ JSAddonId* +AddonPathService::FindAddonId(const nsAString& path) +{ + // If no service has been created, then we're not going to find anything. + if (!sInstance) { + return nullptr; + } + + return sInstance->Find(path); +} + +NS_IMETHODIMP +AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdString) +{ + JSAddonId* addonId = ConvertAddonId(addonIdString); + + // Add the new path in sorted order. + PathEntryComparator comparator; + mPaths.InsertElementSorted(PathEntry(path, addonId), comparator); + return NS_OK; +} + +NS_IMETHODIMP +AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) +{ + if (JSAddonId* id = MapURIToAddonID(aURI)) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); + AssignJSFlatString(addonIdString, flat); + } + return NS_OK; +} + +static nsresult +ResolveURI(nsIURI* aURI, nsAString& out) +{ + bool equals; + nsresult rv; + nsCOMPtr uri; + nsAutoCString spec; + + // Resolve resource:// URIs. At the end of this if/else block, we + // have both spec and uri variables identifying the same URI. + if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) { + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr ph; + rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr irph(do_QueryInterface(ph, &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + rv = irph->ResolveURI(aURI, spec); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) { + nsCOMPtr chromeReg = + mozilla::services::GetChromeRegistryService(); + if (NS_WARN_IF(!chromeReg)) + return NS_ERROR_UNEXPECTED; + + rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } else { + uri = aURI; + } + + if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) { + nsCOMPtr jarURI = do_QueryInterface(uri, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr jarFileURI; + rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + return ResolveURI(jarFileURI, out); + } + + if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) { + nsCOMPtr baseFileURL = do_QueryInterface(uri, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr file; + rv = baseFileURL->GetFile(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + return file->GetPath(out); + } + return NS_ERROR_FAILURE; +} + +JSAddonId* +MapURIToAddonID(nsIURI* aURI) +{ + if (!NS_IsMainThread() || !XRE_IsParentProcess()) { + return nullptr; + } + + bool equals; + nsresult rv; + if (NS_SUCCEEDED(aURI->SchemeIs("moz-extension", &equals)) && equals) { + nsCOMPtr service = do_GetService("@mozilla.org/addons/policy-service;1"); + if (service) { + nsString addonId; + rv = service->ExtensionURIToAddonId(aURI, addonId); + if (NS_FAILED(rv)) + return nullptr; + + return ConvertAddonId(addonId); + } + } + + nsAutoString filePath; + rv = ResolveURI(aURI, filePath); + if (NS_FAILED(rv)) + return nullptr; + + nsCOMPtr greJar = Omnijar::GetPath(Omnijar::GRE); + nsCOMPtr appJar = Omnijar::GetPath(Omnijar::APP); + if (greJar && appJar) { + nsAutoString greJarString, appJarString; + if (NS_FAILED(greJar->GetPath(greJarString)) || NS_FAILED(appJar->GetPath(appJarString))) + return nullptr; + + // If |aURI| is part of either Omnijar, then it can't be part of an + // add-on. This catches pretty much all URLs for Firefox content. + if (filePath.Equals(greJarString) || filePath.Equals(appJarString)) + return nullptr; + } + + // If it's not part of Firefox, we resort to binary searching through the + // add-on paths. + return AddonPathService::FindAddonId(filePath); +} + +} // namespace mozilla diff --git a/toolkit/mozapps/extensions/AddonPathService.h b/toolkit/mozapps/extensions/AddonPathService.h new file mode 100644 index 000000000..f739b018f --- /dev/null +++ b/toolkit/mozapps/extensions/AddonPathService.h @@ -0,0 +1,55 @@ +//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef AddonPathService_h +#define AddonPathService_h + +#include "amIAddonPathService.h" +#include "nsString.h" +#include "nsTArray.h" + +class nsIURI; +class JSAddonId; + +namespace mozilla { + +JSAddonId* +MapURIToAddonID(nsIURI* aURI); + +class AddonPathService final : public amIAddonPathService +{ +public: + AddonPathService(); + + static AddonPathService* GetInstance(); + + JSAddonId* Find(const nsAString& path); + static JSAddonId* FindAddonId(const nsAString& path); + + NS_DECL_ISUPPORTS + NS_DECL_AMIADDONPATHSERVICE + + struct PathEntry + { + nsString mPath; + JSAddonId* mAddonId; + + PathEntry(const nsAString& aPath, JSAddonId* aAddonId) + : mPath(aPath), mAddonId(aAddonId) + {} + }; + +private: + virtual ~AddonPathService(); + + // Paths are stored sorted in order of their mPath. + nsTArray mPaths; + + static AddonPathService* sInstance; +}; + +} // namespace mozilla + +#endif diff --git a/toolkit/mozapps/extensions/ChromeManifestParser.jsm b/toolkit/mozapps/extensions/ChromeManifestParser.jsm new file mode 100644 index 000000000..63f1db785 --- /dev/null +++ b/toolkit/mozapps/extensions/ChromeManifestParser.jsm @@ -0,0 +1,157 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["ChromeManifestParser"]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +const MSG_JAR_FLUSH = "AddonJarFlush"; + + +/** + * Sends local and remote notifications to flush a JAR file cache entry + * + * @param aJarFile + * The ZIP/XPI/JAR file as a nsIFile + */ +function flushJarCache(aJarFile) { + Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null); + Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster) + .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path); +} + + +/** + * Parses chrome manifest files. + */ +this.ChromeManifestParser = { + + /** + * Reads and parses a chrome manifest file located at a specified URI, and all + * secondary manifests it references. + * + * @param aURI + * A nsIURI pointing to a chrome manifest. + * Typically a file: or jar: URI. + * @return Array of objects describing each manifest instruction, in the form: + * { type: instruction-type, baseURI: string-uri, args: [arguments] } + **/ + parseSync: function(aURI) { + function parseLine(aLine) { + let line = aLine.trim(); + if (line.length == 0 || line.charAt(0) == '#') + return; + let tokens = line.split(/\s+/); + let type = tokens.shift(); + if (type == "manifest") { + let uri = NetUtil.newURI(tokens.shift(), null, aURI); + data = data.concat(this.parseSync(uri)); + } else { + data.push({type: type, baseURI: baseURI, args: tokens}); + } + } + + let contents = ""; + try { + if (aURI.scheme == "jar") + contents = this._readFromJar(aURI); + else + contents = this._readFromFile(aURI); + } catch (e) { + // Silently fail. + } + + if (!contents) + return []; + + let baseURI = NetUtil.newURI(".", null, aURI).spec; + + let data = []; + let lines = contents.split("\n"); + lines.forEach(parseLine.bind(this)); + return data; + }, + + _readFromJar: function(aURI) { + let data = ""; + let entries = []; + let readers = []; + + try { + // Deconstrict URI, which can be nested jar: URIs. + let uri = aURI.clone(); + while (uri instanceof Ci.nsIJARURI) { + entries.push(uri.JAREntry); + uri = uri.JARFile; + } + + // Open the base jar. + let reader = Cc["@mozilla.org/libjar/zip-reader;1"]. + createInstance(Ci.nsIZipReader); + reader.open(uri.QueryInterface(Ci.nsIFileURL).file); + readers.push(reader); + + // Open the nested jars. + for (let i = entries.length - 1; i > 0; i--) { + let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"]. + createInstance(Ci.nsIZipReader); + innerReader.openInner(reader, entries[i]); + readers.push(innerReader); + reader = innerReader; + } + + // First entry is the actual file we want to read. + let zis = reader.getInputStream(entries[0]); + data = NetUtil.readInputStreamToString(zis, zis.available()); + } + finally { + // Close readers in reverse order. + for (let i = readers.length - 1; i >= 0; i--) { + readers[i].close(); + flushJarCache(readers[i].file); + } + } + + return data; + }, + + _readFromFile: function(aURI) { + let file = aURI.QueryInterface(Ci.nsIFileURL).file; + if (!file.exists() || !file.isFile()) + return ""; + + let data = ""; + let fis = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + try { + fis.init(file, -1, -1, false); + data = NetUtil.readInputStreamToString(fis, fis.available()); + } finally { + fis.close(); + } + return data; + }, + + /** + * Detects if there were any instructions of a specified type in a given + * chrome manifest. + * + * @param aManifest + * Manifest data, as returned by ChromeManifestParser.parseSync(). + * @param aType + * Instruction type to filter by. + * @return True if any matching instructions were found in the manifest. + */ + hasType: function(aManifest, aType) { + return aManifest.some(entry => entry.type == aType); + } +}; diff --git a/toolkit/mozapps/extensions/DeferredSave.jsm b/toolkit/mozapps/extensions/DeferredSave.jsm new file mode 100644 index 000000000..89f82b265 --- /dev/null +++ b/toolkit/mozapps/extensions/DeferredSave.jsm @@ -0,0 +1,275 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cu = Components.utils; +const Cc = Components.classes; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/osfile.jsm"); +/* globals OS*/ +Cu.import("resource://gre/modules/Promise.jsm"); + +// Make it possible to mock out timers for testing +var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + +this.EXPORTED_SYMBOLS = ["DeferredSave"]; + +// If delay parameter is not provided, default is 50 milliseconds. +const DEFAULT_SAVE_DELAY_MS = 50; + +Cu.import("resource://gre/modules/Log.jsm"); +// Configure a logger at the parent 'DeferredSave' level to format +// messages for all the modules under DeferredSave.* +const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave"; +var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID); +parentLogger.level = Log.Level.Warn; +var formatter = new Log.BasicFormatter(); +// Set parent logger (and its children) to append to +// the Javascript section of the Browser Console +parentLogger.addAppender(new Log.ConsoleAppender(formatter)); +// Set parent logger (and its children) to +// also append to standard out +parentLogger.addAppender(new Log.DumpAppender(formatter)); + +// Provide the ability to enable/disable logging +// messages at runtime. +// If the "extensions.logging.enabled" preference is +// missing or 'false', messages at the WARNING and higher +// severity should be logged to the JS console and standard error. +// If "extensions.logging.enabled" is set to 'true', messages +// at DEBUG and higher should go to JS console and standard error. +Cu.import("resource://gre/modules/Services.jsm"); + +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; + +/** +* Preference listener which listens for a change in the +* "extensions.logging.enabled" preference and changes the logging level of the +* parent 'addons' level logger accordingly. +*/ +var PrefObserver = { + init: function() { + Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic == "xpcom-shutdown") { + Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); + Services.obs.removeObserver(this, "xpcom-shutdown"); + } + else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { + let debugLogEnabled = false; + try { + debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); + } + catch (e) { + } + if (debugLogEnabled) { + parentLogger.level = Log.Level.Debug; + } + else { + parentLogger.level = Log.Level.Warn; + } + } + } +}; + +PrefObserver.init(); + +/** + * A module to manage deferred, asynchronous writing of data files + * to disk. Writing is deferred by waiting for a specified delay after + * a request to save the data, before beginning to write. If more than + * one save request is received during the delay, all requests are + * fulfilled by a single write. + * + * @constructor + * @param aPath + * String representing the full path of the file where the data + * is to be written. + * @param aDataProvider + * Callback function that takes no argument and returns the data to + * be written. If aDataProvider returns an ArrayBufferView, the + * bytes it contains are written to the file as is. + * If aDataProvider returns a String the data are UTF-8 encoded + * and then written to the file. + * @param [optional] aDelay + * The delay in milliseconds between the first saveChanges() call + * that marks the data as needing to be saved, and when the DeferredSave + * begins writing the data to disk. Default 50 milliseconds. + */ +this.DeferredSave = function(aPath, aDataProvider, aDelay) { + // Create a new logger (child of 'DeferredSave' logger) + // for use by this particular instance of DeferredSave object + let leafName = OS.Path.basename(aPath); + let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName; + this.logger = Log.repository.getLogger(logger_id); + + // @type {Deferred|null}, null when no data needs to be written + // @resolves with the result of OS.File.writeAtomic when all writes complete + // @rejects with the error from OS.File.writeAtomic if the write fails, + // or with the error from aDataProvider() if that throws. + this._pending = null; + + // @type {Promise}, completes when the in-progress write (if any) completes, + // kept as a resolved promise at other times to simplify logic. + // Because _deferredSave() always uses _writing.then() to execute + // its next action, we don't need a special case for whether a write + // is in progress - if the previous write is complete (and the _writing + // promise is already resolved/rejected), _writing.then() starts + // the next action immediately. + // + // @resolves with the result of OS.File.writeAtomic + // @rejects with the error from OS.File.writeAtomic + this._writing = Promise.resolve(0); + + // Are we currently waiting for a write to complete + this.writeInProgress = false; + + this._path = aPath; + this._dataProvider = aDataProvider; + + this._timer = null; + + // 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 + this._delay = DEFAULT_SAVE_DELAY_MS; +} + +this.DeferredSave.prototype = { + get dirty() { + return this._pending || this.writeInProgress; + }, + + get lastError() { + return this._lastError; + }, + + // Start the pending timer if data is dirty + _startTimer: function() { + if (!this._pending) { + return; + } + + this.logger.debug("Starting timer"); + if (!this._timer) + this._timer = MakeTimer(); + this._timer.initWithCallback(() => this._deferredSave(), + this._delay, Ci.nsITimer.TYPE_ONE_SHOT); + }, + + /** + * Mark the current stored data dirty, and schedule a flush to disk + * @return A Promise that will be resolved after the data is written to disk; + * the promise is resolved with the number of bytes written. + */ + saveChanges: function() { + this.logger.debug("Save changes"); + if (!this._pending) { + if (this.writeInProgress) { + this.logger.debug("Data changed while write in progress"); + this.overlappedSaves++; + } + this._pending = Promise.defer(); + // Wait until the most recent write completes or fails (if it hasn't already) + // and then restart our timer + this._writing.then(count => this._startTimer(), error => this._startTimer()); + } + return this._pending.promise; + }, + + _deferredSave: function() { + let pending = this._pending; + this._pending = null; + let writing = this._writing; + this._writing = pending.promise; + + // In either the success or the exception handling case, we don't need to handle + // the error from _writing here; it's already being handled in another then() + let toSave = null; + try { + toSave = this._dataProvider(); + } + catch (e) { + this.logger.error("Deferred save dataProvider failed", e); + writing.then(null, error => {}) + .then(count => { + pending.reject(e); + }); + return; + } + + writing.then(null, error => { return 0; }) + .then(count => { + this.logger.debug("Starting write"); + this.totalSaves++; + this.writeInProgress = true; + + OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"}) + .then( + result => { + this._lastError = null; + this.writeInProgress = false; + this.logger.debug("Write succeeded"); + pending.resolve(result); + }, + error => { + this._lastError = error; + this.writeInProgress = false; + this.logger.warn("Write failed", error); + pending.reject(error); + }); + }); + }, + + /** + * Immediately save the dirty data to disk, skipping + * the delay of normal operation. Note that the write + * still happens asynchronously in the worker + * thread from OS.File. + * + * There are four possible situations: + * 1) Nothing to flush + * 2) Data is not currently being written, in-memory copy is dirty + * 3) Data is currently being written, in-memory copy is clean + * 4) Data is being written and in-memory copy is dirty + * + * @return Promise that will resolve when all in-memory data + * has finished being flushed, returning the number of bytes + * written. If all in-memory data is clean, completes with the + * result of the most recent write. + */ + flush: function() { + // If we have pending changes, cancel our timer and set up the write + // immediately (_deferredSave queues the write for after the most + // recent write completes, if it hasn't already) + if (this._pending) { + this.logger.debug("Flush called while data is dirty"); + if (this._timer) { + this._timer.cancel(); + this._timer = null; + } + this._deferredSave(); + } + + return this._writing; + } +}; diff --git a/toolkit/mozapps/extensions/LightweightThemeManager.jsm b/toolkit/mozapps/extensions/LightweightThemeManager.jsm new file mode 100644 index 000000000..5dd41831d --- /dev/null +++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm @@ -0,0 +1,909 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["LightweightThemeManager"]; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/AddonManager.jsm"); +/* globals AddonManagerPrivate*/ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ID_SUFFIX = "@personas.mozilla.org"; +const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect"; +const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; +const PREF_EM_DSS_ENABLED = "extensions.dss.enabled"; +const ADDON_TYPE = "theme"; + +const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; + +const STRING_TYPE_NAME = "type.%ID%.name"; + +const DEFAULT_MAX_USED_THEMES_COUNT = 30; + +const MAX_PREVIEW_SECONDS = 30; + +const MANDATORY = ["id", "name", "headerURL"]; +const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL", + "previewURL", "author", "description", "homepageURL", + "updateURL", "version"]; + +const PERSIST_ENABLED = true; +const PERSIST_BYPASS_CACHE = false; +const PERSIST_FILES = { + headerURL: "lightweighttheme-header", + footerURL: "lightweighttheme-footer" +}; + +XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer", + "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest", + "resource://gre/modules/ServiceRequest.jsm"); + + +XPCOMUtils.defineLazyGetter(this, "_prefs", () => { + return Services.prefs.getBranch("lightweightThemes."); +}); + +Object.defineProperty(this, "_maxUsedThemes", { + get: function() { + delete this._maxUsedThemes; + try { + this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes"); + } + catch (e) { + this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; + } + return this._maxUsedThemes; + }, + + set: function(val) { + delete this._maxUsedThemes; + return this._maxUsedThemes = val; + }, + configurable: true, +}); + +// Holds the ID of the theme being enabled or disabled while sending out the +// events so cached AddonWrapper instances can return correct values for +// permissions and pendingOperations +var _themeIDBeingEnabled = null; +var _themeIDBeingDisabled = null; + +// Convert from the old storage format (in which the order of usedThemes +// was combined with isThemeSelected to determine which theme was selected) +// to the new one (where a selectedThemeID determines which theme is selected). +(function() { + let wasThemeSelected = false; + try { + wasThemeSelected = _prefs.getBoolPref("isThemeSelected"); + } catch (e) { } + + if (wasThemeSelected) { + _prefs.clearUserPref("isThemeSelected"); + let themes = []; + try { + themes = JSON.parse(_prefs.getComplexValue("usedThemes", + Ci.nsISupportsString).data); + } catch (e) { } + + if (Array.isArray(themes) && themes[0]) { + _prefs.setCharPref("selectedThemeID", themes[0].id); + } + } +})(); + +this.LightweightThemeManager = { + get name() { + return "LightweightThemeManager"; + }, + + // Themes that can be added for an application. They can't be removed, and + // will always show up at the top of the list. + _builtInThemes: new Map(), + + get usedThemes () { + let themes = []; + try { + themes = JSON.parse(_prefs.getComplexValue("usedThemes", + Ci.nsISupportsString).data); + } catch (e) { } + + themes.push(...this._builtInThemes.values()); + return themes; + }, + + get currentTheme () { + let selectedThemeID = null; + try { + selectedThemeID = _prefs.getCharPref("selectedThemeID"); + } catch (e) {} + + let data = null; + if (selectedThemeID) { + data = this.getUsedTheme(selectedThemeID); + } + return data; + }, + + get currentThemeForDisplay () { + var data = this.currentTheme; + + if (data && PERSIST_ENABLED) { + for (let key in PERSIST_FILES) { + try { + if (data[key] && _prefs.getBoolPref("persisted." + key)) + data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec + + "?" + data.id + ";" + _version(data); + } catch (e) {} + } + } + + return data; + }, + + set currentTheme (aData) { + return _setCurrentTheme(aData, false); + }, + + setLocalTheme: function(aData) { + _setCurrentTheme(aData, true); + }, + + getUsedTheme: function(aId) { + var usedThemes = this.usedThemes; + for (let usedTheme of usedThemes) { + if (usedTheme.id == aId) + return usedTheme; + } + return null; + }, + + forgetUsedTheme: function(aId) { + let theme = this.getUsedTheme(aId); + if (!theme || LightweightThemeManager._builtInThemes.has(theme.id)) + return; + + let wrapper = new AddonWrapper(theme); + AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); + + var currentTheme = this.currentTheme; + if (currentTheme && currentTheme.id == aId) { + this.themeChanged(null); + AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false); + } + + _updateUsedThemes(_usedThemesExceptId(aId)); + AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); + }, + + addBuiltInTheme: function(theme) { + if (!theme || !theme.id || this.usedThemes.some(t => t.id == theme.id)) { + throw new Error("Trying to add invalid builtIn theme"); + } + + this._builtInThemes.set(theme.id, theme); + + if (_prefs.getCharPref("selectedThemeID") == theme.id) { + this.currentTheme = theme; + } + }, + + forgetBuiltInTheme: function(id) { + if (!this._builtInThemes.has(id)) { + let currentTheme = this.currentTheme; + if (currentTheme && currentTheme.id == id) { + this.currentTheme = null; + } + } + return this._builtInThemes.delete(id); + }, + + clearBuiltInThemes: function() { + for (let id of this._builtInThemes.keys()) { + this.forgetBuiltInTheme(id); + } + }, + + previewTheme: function(aData) { + let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + cancel.data = false; + Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested", + JSON.stringify(aData)); + if (cancel.data) + return; + + if (_previewTimer) + _previewTimer.cancel(); + else + _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + _previewTimer.initWithCallback(_previewTimerCallback, + MAX_PREVIEW_SECONDS * 1000, + _previewTimer.TYPE_ONE_SHOT); + + _notifyWindows(aData); + }, + + resetPreview: function() { + if (_previewTimer) { + _previewTimer.cancel(); + _previewTimer = null; + _notifyWindows(this.currentThemeForDisplay); + } + }, + + parseTheme: function(aString, aBaseURI) { + try { + return _sanitizeTheme(JSON.parse(aString), aBaseURI, false); + } catch (e) { + return null; + } + }, + + updateCurrentTheme: function() { + try { + if (!_prefs.getBoolPref("update.enabled")) + return; + } catch (e) { + return; + } + + var theme = this.currentTheme; + if (!theme || !theme.updateURL) + return; + + var req = new ServiceRequest(); + + req.mozBackgroundRequest = true; + req.overrideMimeType("text/plain"); + req.open("GET", theme.updateURL, true); + // Prevent the request from reading from the cache. + req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; + // Prevent the request from writing to the cache. + req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; + + req.addEventListener("load", () => { + if (req.status != 200) + return; + + let newData = this.parseTheme(req.responseText, theme.updateURL); + if (!newData || + newData.id != theme.id || + _version(newData) == _version(theme)) + return; + + var currentTheme = this.currentTheme; + if (currentTheme && currentTheme.id == theme.id) + this.currentTheme = newData; + }, false); + + req.send(null); + }, + + /** + * Switches to a new lightweight theme. + * + * @param aData + * The lightweight theme to switch to + */ + themeChanged: function(aData) { + if (_previewTimer) { + _previewTimer.cancel(); + _previewTimer = null; + } + + if (aData) { + let usedThemes = _usedThemesExceptId(aData.id); + usedThemes.unshift(aData); + _updateUsedThemes(usedThemes); + if (PERSIST_ENABLED) { + LightweightThemeImageOptimizer.purge(); + _persistImages(aData, function() { + _notifyWindows(this.currentThemeForDisplay); + }.bind(this)); + } + } + + if (aData) + _prefs.setCharPref("selectedThemeID", aData.id); + else + _prefs.setCharPref("selectedThemeID", ""); + + _notifyWindows(aData); + Services.obs.notifyObservers(null, "lightweight-theme-changed", null); + }, + + /** + * Starts the Addons provider and enables the new lightweight theme if + * necessary. + */ + startup: function() { + if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) { + let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); + if (id) + this.themeChanged(this.getUsedTheme(id)); + else + this.themeChanged(null); + Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); + } + + _prefs.addObserver("", _prefObserver, false); + }, + + /** + * Shuts down the provider. + */ + shutdown: function() { + _prefs.removeObserver("", _prefObserver); + }, + + /** + * Called when a new add-on has been enabled when only one add-on of that type + * can be enabled. + * + * @param aId + * The ID of the newly enabled add-on + * @param aType + * The type of the newly enabled add-on + * @param aPendingRestart + * true if the newly enabled add-on will only become enabled after a + * restart + */ + addonChanged: function(aId, aType, aPendingRestart) { + if (aType != ADDON_TYPE) + return; + + let id = _getInternalID(aId); + let current = this.currentTheme; + + try { + let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); + if (id == next && aPendingRestart) + return; + + Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); + if (next) { + AddonManagerPrivate.callAddonListeners("onOperationCancelled", + new AddonWrapper(this.getUsedTheme(next))); + } + else if (id == current.id) { + AddonManagerPrivate.callAddonListeners("onOperationCancelled", + new AddonWrapper(current)); + return; + } + } + catch (e) { + } + + if (current) { + if (current.id == id) + return; + _themeIDBeingDisabled = current.id; + let wrapper = new AddonWrapper(current); + if (aPendingRestart) { + Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, ""); + AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true); + } + else { + AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); + this.themeChanged(null); + AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); + } + _themeIDBeingDisabled = null; + } + + if (id) { + let theme = this.getUsedTheme(id); + _themeIDBeingEnabled = id; + let wrapper = new AddonWrapper(theme); + if (aPendingRestart) { + AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true); + Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id); + + // Flush the preferences to disk so they survive any crash + Services.prefs.savePrefFile(null); + } + else { + AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); + this.themeChanged(theme); + AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); + } + _themeIDBeingEnabled = null; + } + }, + + /** + * Called to get an Addon with a particular ID. + * + * @param aId + * The ID of the add-on to retrieve + * @param aCallback + * A callback to pass the Addon to + */ + getAddonByID: function(aId, aCallback) { + let id = _getInternalID(aId); + if (!id) { + aCallback(null); + return; + } + + let theme = this.getUsedTheme(id); + if (!theme) { + aCallback(null); + return; + } + + aCallback(new AddonWrapper(theme)); + }, + + /** + * Called to get Addons of a particular type. + * + * @param aTypes + * An array of types to fetch. Can be null to get all types. + * @param aCallback + * A callback to pass an array of Addons to + */ + getAddonsByTypes: function(aTypes, aCallback) { + if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) { + aCallback([]); + return; + } + + aCallback(this.usedThemes.map(a => new AddonWrapper(a))); + }, +}; + +const wrapperMap = new WeakMap(); +let themeFor = wrapper => wrapperMap.get(wrapper); + +/** + * The AddonWrapper wraps lightweight theme to provide the data visible to + * consumers of the AddonManager API. + */ +function AddonWrapper(aTheme) { + wrapperMap.set(this, aTheme); +} + +AddonWrapper.prototype = { + get id() { + return themeFor(this).id + ID_SUFFIX; + }, + + get type() { + return ADDON_TYPE; + }, + + get isActive() { + let current = LightweightThemeManager.currentTheme; + if (current) + return themeFor(this).id == current.id; + return false; + }, + + get name() { + return themeFor(this).name; + }, + + get version() { + let theme = themeFor(this); + return "version" in theme ? theme.version : ""; + }, + + get creator() { + let theme = themeFor(this); + return "author" in theme ? new AddonManagerPrivate.AddonAuthor(theme.author) : null; + }, + + get screenshots() { + let url = themeFor(this).previewURL; + return [new AddonManagerPrivate.AddonScreenshot(url)]; + }, + + get pendingOperations() { + let pending = AddonManager.PENDING_NONE; + if (this.isActive == this.userDisabled) + pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE; + return pending; + }, + + get operationsRequiringRestart() { + // If a non-default theme is in use then a restart will be required to + // enable lightweight themes unless dynamic theme switching is enabled + if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) { + try { + if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED)) + return AddonManager.OP_NEEDS_RESTART_NONE; + } + catch (e) { + } + return AddonManager.OP_NEEDS_RESTART_ENABLE; + } + + return AddonManager.OP_NEEDS_RESTART_NONE; + }, + + get size() { + // The size changes depending on whether the theme is in use or not, this is + // probably not worth exposing. + return null; + }, + + get permissions() { + let permissions = 0; + + // Do not allow uninstall of builtIn themes. + if (!LightweightThemeManager._builtInThemes.has(themeFor(this).id)) + permissions = AddonManager.PERM_CAN_UNINSTALL; + if (this.userDisabled) + permissions |= AddonManager.PERM_CAN_ENABLE; + else + permissions |= AddonManager.PERM_CAN_DISABLE; + return permissions; + }, + + get userDisabled() { + let id = themeFor(this).id; + if (_themeIDBeingEnabled == id) + return false; + if (_themeIDBeingDisabled == id) + return true; + + try { + let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); + return id != toSelect; + } + catch (e) { + let current = LightweightThemeManager.currentTheme; + return !current || current.id != id; + } + }, + + set userDisabled(val) { + if (val == this.userDisabled) + return val; + + if (val) + LightweightThemeManager.currentTheme = null; + else + LightweightThemeManager.currentTheme = themeFor(this); + + return val; + }, + + // Lightweight themes are never disabled by the application + get appDisabled() { + return false; + }, + + // Lightweight themes are always compatible + get isCompatible() { + return true; + }, + + get isPlatformCompatible() { + return true; + }, + + get scope() { + return AddonManager.SCOPE_PROFILE; + }, + + get foreignInstall() { + return false; + }, + + uninstall: function() { + LightweightThemeManager.forgetUsedTheme(themeFor(this).id); + }, + + cancelUninstall: function() { + throw new Error("Theme is not marked to be uninstalled"); + }, + + findUpdates: function(listener, reason, appVersion, platformVersion) { + AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion); + }, + + // Lightweight themes are always compatible + isCompatibleWith: function(appVersion, platformVersion) { + return true; + }, + + // Lightweight themes are always securely updated + get providesUpdatesSecurely() { + return true; + }, + + // Lightweight themes are never blocklisted + get blocklistState() { + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + } +}; + +["description", "homepageURL", "iconURL"].forEach(function(prop) { + Object.defineProperty(AddonWrapper.prototype, prop, { + get: function() { + let theme = themeFor(this); + return prop in theme ? theme[prop] : null; + }, + enumarable: true, + }); +}); + +["installDate", "updateDate"].forEach(function(prop) { + Object.defineProperty(AddonWrapper.prototype, prop, { + get: function() { + let theme = themeFor(this); + return prop in theme ? new Date(theme[prop]) : null; + }, + enumarable: true, + }); +}); + +/** + * Converts the ID used by the public AddonManager API to an lightweight theme + * ID. + * + * @param id + * The ID to be converted + * + * @return the lightweight theme ID or null if the ID was not for a lightweight + * theme. + */ +function _getInternalID(id) { + if (!id) + return null; + let len = id.length - ID_SUFFIX.length; + if (len > 0 && id.substring(len) == ID_SUFFIX) + return id.substring(0, len); + return null; +} + +function _setCurrentTheme(aData, aLocal) { + aData = _sanitizeTheme(aData, null, aLocal); + + let needsRestart = (ADDON_TYPE == "theme") && + Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN); + + let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + cancel.data = false; + Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested", + JSON.stringify(aData)); + + if (aData) { + let theme = LightweightThemeManager.getUsedTheme(aData.id); + let isInstall = !theme || theme.version != aData.version; + if (isInstall) { + aData.updateDate = Date.now(); + if (theme && "installDate" in theme) + aData.installDate = theme.installDate; + else + aData.installDate = aData.updateDate; + + var oldWrapper = theme ? new AddonWrapper(theme) : null; + var wrapper = new AddonWrapper(aData); + AddonManagerPrivate.callInstallListeners("onExternalInstall", null, + wrapper, oldWrapper, false); + AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false); + } + + let current = LightweightThemeManager.currentTheme; + let usedThemes = _usedThemesExceptId(aData.id); + if (current && current.id != aData.id) + usedThemes.splice(1, 0, aData); + else + usedThemes.unshift(aData); + _updateUsedThemes(usedThemes); + + if (isInstall) + AddonManagerPrivate.callAddonListeners("onInstalled", wrapper); + } + + if (cancel.data) + return null; + + AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null, + ADDON_TYPE, needsRestart); + + return LightweightThemeManager.currentTheme; +} + +function _sanitizeTheme(aData, aBaseURI, aLocal) { + if (!aData || typeof aData != "object") + return null; + + var resourceProtocols = ["http", "https", "resource"]; + if (aLocal) + resourceProtocols.push("file"); + var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):"); + + function sanitizeProperty(prop) { + if (!(prop in aData)) + return null; + if (typeof aData[prop] != "string") + return null; + let val = aData[prop].trim(); + if (!val) + return null; + + if (!/URL$/.test(prop)) + return val; + + try { + val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec; + if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val)) + return val; + return null; + } + catch (e) { + return null; + } + } + + let result = {}; + for (let mandatoryProperty of MANDATORY) { + let val = sanitizeProperty(mandatoryProperty); + if (!val) + throw Components.results.NS_ERROR_INVALID_ARG; + result[mandatoryProperty] = val; + } + + for (let optionalProperty of OPTIONAL) { + let val = sanitizeProperty(optionalProperty); + if (!val) + continue; + result[optionalProperty] = val; + } + + return result; +} + +function _usedThemesExceptId(aId) { + return LightweightThemeManager.usedThemes.filter(function(t) { + return "id" in t && t.id != aId; + }); +} + +function _version(aThemeData) { + return aThemeData.version || ""; +} + +function _makeURI(aURL, aBaseURI) { + return Services.io.newURI(aURL, null, aBaseURI); +} + +function _updateUsedThemes(aList) { + // Remove app-specific themes before saving them to the usedThemes pref. + aList = aList.filter(theme => !LightweightThemeManager._builtInThemes.has(theme.id)); + + // Send uninstall events for all themes that need to be removed. + while (aList.length > _maxUsedThemes) { + let wrapper = new AddonWrapper(aList[aList.length - 1]); + AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); + aList.pop(); + AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); + } + + var str = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + str.data = JSON.stringify(aList); + _prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str); + + Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null); +} + +function _notifyWindows(aThemeData) { + Services.obs.notifyObservers(null, "lightweight-theme-styling-update", + JSON.stringify(aThemeData)); +} + +var _previewTimer; +var _previewTimerCallback = { + notify: function() { + LightweightThemeManager.resetPreview(); + } +}; + +/** + * Called when any of the lightweightThemes preferences are changed. + */ +function _prefObserver(aSubject, aTopic, aData) { + switch (aData) { + case "maxUsedThemes": + try { + _maxUsedThemes = _prefs.getIntPref(aData); + } + catch (e) { + _maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; + } + // Update the theme list to remove any themes over the number we keep + _updateUsedThemes(LightweightThemeManager.usedThemes); + break; + } +} + +function _persistImages(aData, aCallback) { + function onSuccess(key) { + return function () { + let current = LightweightThemeManager.currentTheme; + if (current && current.id == aData.id) { + _prefs.setBoolPref("persisted." + key, true); + } + if (--numFilesToPersist == 0 && aCallback) { + aCallback(); + } + }; + } + + let numFilesToPersist = 0; + for (let key in PERSIST_FILES) { + _prefs.setBoolPref("persisted." + key, false); + if (aData[key]) { + numFilesToPersist++; + _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key)); + } + } +} + +function _getLocalImageURI(localFileName) { + var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + localFile.append(localFileName); + return Services.io.newFileURI(localFile); +} + +function _persistImage(sourceURL, localFileName, successCallback) { + if (/^(file|resource):/.test(sourceURL)) + return; + + var targetURI = _getLocalImageURI(localFileName); + var sourceURI = _makeURI(sourceURL); + + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + + persist.persistFlags = + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION | + (PERSIST_BYPASS_CACHE ? + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE : + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE); + + persist.progressListener = new _persistProgressListener(successCallback); + + persist.saveURI(sourceURI, null, + null, Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE, + null, null, targetURI, null); +} + +function _persistProgressListener(successCallback) { + this.onLocationChange = function() {}; + this.onProgressChange = function() {}; + this.onStatusChange = function() {}; + this.onSecurityChange = function() {}; + this.onStateChange = function(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aRequest && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + try { + if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) { + // success + successCallback(); + return; + } + } catch (e) { } + // failure + } + }; +} + +AddonManagerPrivate.registerProvider(LightweightThemeManager, [ + new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS, + STRING_TYPE_NAME, + AddonManager.VIEW_TYPE_LIST, 5000) +]); diff --git a/toolkit/mozapps/extensions/addonManager.js b/toolkit/mozapps/extensions/addonManager.js new file mode 100644 index 000000000..d34cbaf62 --- /dev/null +++ b/toolkit/mozapps/extensions/addonManager.js @@ -0,0 +1,296 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This component serves as integration between the platform and AddonManager. + * It is responsible for initializing and shutting down the AddonManager as well + * as passing new installs from webpages to the AddonManager. + */ + +"use strict"; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +// The old XPInstall error codes +const EXECUTION_ERROR = -203; +const CANT_READ_ARCHIVE = -207; +const USER_CANCELLED = -210; +const DOWNLOAD_ERROR = -228; +const UNSUPPORTED_TYPE = -244; +const SUCCESS = 0; + +const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; +const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; +const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; + +const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; +const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; +const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; +const MSG_INSTALL_CLEANUP = "WebAPICleanup"; +const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; +const MSG_ADDON_EVENT = "WebAPIAddonEvent"; + +const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js"; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +var gSingleton = null; + +function amManager() { + Cu.import("resource://gre/modules/AddonManager.jsm"); + /* globals AddonManagerPrivate*/ + + Services.mm.loadFrameScript(CHILD_SCRIPT, true); + Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this); + Services.mm.addMessageListener(MSG_INSTALL_ADDONS, this); + Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this); + Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this); + Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this); + + Services.obs.addObserver(this, "message-manager-close", false); + Services.obs.addObserver(this, "message-manager-disconnect", false); + + AddonManager.webAPI.setEventHandler(this.sendEvent); + + // Needed so receiveMessage can be called directly by JS callers + this.wrappedJSObject = this; +} + +amManager.prototype = { + observe: function(aSubject, aTopic, aData) { + switch (aTopic) { + case "addons-startup": + AddonManagerPrivate.startup(); + break; + + case "message-manager-close": + case "message-manager-disconnect": + this.childClosed(aSubject); + break; + } + }, + + /** + * @see amIAddonManager.idl + */ + mapURIToAddonID: function(uri, id) { + id.value = AddonManager.mapURIToAddonID(uri); + return !!id.value; + }, + + /** + * @see amIWebInstaller.idl + */ + isInstallEnabled: function(aMimetype, aReferer) { + return AddonManager.isInstallEnabled(aMimetype); + }, + + /** + * @see amIWebInstaller.idl + */ + installAddonsFromWebpage: function(aMimetype, aBrowser, aInstallingPrincipal, + aUris, aHashes, aNames, aIcons, aCallback) { + if (aUris.length == 0) + return false; + + let retval = true; + if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) { + aCallback = null; + retval = false; + } + + let installs = []; + function buildNextInstall() { + if (aUris.length == 0) { + AddonManager.installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, installs); + return; + } + let uri = aUris.shift(); + AddonManager.getInstallForURL(uri, function(aInstall) { + function callCallback(aUri, aStatus) { + try { + aCallback.onInstallEnded(aUri, aStatus); + } + catch (e) { + Components.utils.reportError(e); + } + } + + if (aInstall) { + installs.push(aInstall); + if (aCallback) { + aInstall.addListener({ + onDownloadCancelled: function(aInstall) { + callCallback(uri, USER_CANCELLED); + }, + + onDownloadFailed: function(aInstall) { + if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) + callCallback(uri, CANT_READ_ARCHIVE); + else + callCallback(uri, DOWNLOAD_ERROR); + }, + + onInstallFailed: function(aInstall) { + callCallback(uri, EXECUTION_ERROR); + }, + + onInstallEnded: function(aInstall, aStatus) { + callCallback(uri, SUCCESS); + } + }); + } + } + else if (aCallback) { + aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE); + } + buildNextInstall(); + }, aMimetype, aHashes.shift(), aNames.shift(), aIcons.shift(), null, aBrowser); + } + buildNextInstall(); + + return retval; + }, + + notify: function(aTimer) { + AddonManagerPrivate.backgroundUpdateTimerHandler(); + }, + + // Maps message manager instances for content processes to the associated + // AddonListener instances. + addonListeners: new Map(), + + _addAddonListener(target) { + if (!this.addonListeners.has(target)) { + let handler = (event, id, needsRestart) => { + target.sendAsyncMessage(MSG_ADDON_EVENT, {event, id, needsRestart}); + }; + let listener = { + onEnabling: (addon, needsRestart) => handler("onEnabling", addon.id, needsRestart), + onEnabled: (addon) => handler("onEnabled", addon.id, false), + onDisabling: (addon, needsRestart) => handler("onDisabling", addon.id, needsRestart), + onDisabled: (addon) => handler("onDisabled", addon.id, false), + onInstalling: (addon, needsRestart) => handler("onInstalling", addon.id, needsRestart), + onInstalled: (addon) => handler("onInstalled", addon.id, false), + onUninstalling: (addon, needsRestart) => handler("onUninstalling", addon.id, needsRestart), + onUninstalled: (addon) => handler("onUninstalled", addon.id, false), + onOperationCancelled: (addon) => handler("onOperationCancelled", addon.id, false), + }; + this.addonListeners.set(target, listener); + AddonManager.addAddonListener(listener); + } + }, + + _removeAddonListener(target) { + if (this.addonListeners.has(target)) { + AddonManager.removeAddonListener(this.addonListeners.get(target)); + this.addonListeners.delete(target); + } + }, + + /** + * messageManager callback function. + * + * Listens to requests from child processes for InstallTrigger + * activity, and sends back callbacks. + */ + receiveMessage: function(aMessage) { + let payload = aMessage.data; + + switch (aMessage.name) { + case MSG_INSTALL_ENABLED: + return AddonManager.isInstallEnabled(payload.mimetype); + + case MSG_INSTALL_ADDONS: { + let callback = null; + if (payload.callbackID != -1) { + let mm = aMessage.target.messageManager; + callback = { + onInstallEnded: function(url, status) { + mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, { + callbackID: payload.callbackID, + url: url, + status: status + }); + }, + }; + } + + return this.installAddonsFromWebpage(payload.mimetype, + aMessage.target, payload.triggeringPrincipal, payload.uris, + payload.hashes, payload.names, payload.icons, callback); + } + + case MSG_PROMISE_REQUEST: { + let mm = aMessage.target.messageManager; + let resolve = (value) => { + mm.sendAsyncMessage(MSG_PROMISE_RESULT, { + callbackID: payload.callbackID, + resolve: value + }); + } + let reject = (value) => { + mm.sendAsyncMessage(MSG_PROMISE_RESULT, { + callbackID: payload.callbackID, + reject: value + }); + } + + let API = AddonManager.webAPI; + if (payload.type in API) { + API[payload.type](aMessage.target, ...payload.args).then(resolve, reject); + } + else { + reject("Unknown Add-on API request."); + } + break; + } + + case MSG_INSTALL_CLEANUP: { + AddonManager.webAPI.clearInstalls(payload.ids); + break; + } + + case MSG_ADDON_EVENT_REQ: { + let target = aMessage.target.messageManager; + if (payload.enabled) { + this._addAddonListener(target); + } else { + this._removeAddonListener(target); + } + } + } + return undefined; + }, + + childClosed(target) { + AddonManager.webAPI.clearInstallsFrom(target); + this._removeAddonListener(target); + }, + + sendEvent(mm, data) { + mm.sendAsyncMessage(MSG_INSTALL_EVENT, data); + }, + + classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"), + _xpcom_factory: { + createInstance: function(aOuter, aIid) { + if (aOuter != null) + throw Components.Exception("Component does not support aggregation", + Cr.NS_ERROR_NO_AGGREGATION); + + if (!gSingleton) + gSingleton = new amManager(); + return gSingleton.QueryInterface(aIid); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager, + Ci.amIWebInstaller, + Ci.nsITimerCallback, + Ci.nsIObserver, + Ci.nsIMessageListener]) +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]); diff --git a/toolkit/mozapps/extensions/amContentHandler.js b/toolkit/mozapps/extensions/amContentHandler.js new file mode 100644 index 000000000..8dc4dfecd --- /dev/null +++ b/toolkit/mozapps/extensions/amContentHandler.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +const XPI_CONTENT_TYPE = "application/x-xpinstall"; +const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +function amContentHandler() { +} + +amContentHandler.prototype = { + /** + * Handles a new request for an application/x-xpinstall file. + * + * @param aMimetype + * The mimetype of the file + * @param aContext + * The context passed to nsIChannel.asyncOpen + * @param aRequest + * The nsIRequest dealing with the content + */ + handleContent: function(aMimetype, aContext, aRequest) { + if (aMimetype != XPI_CONTENT_TYPE) + throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; + + if (!(aRequest instanceof Ci.nsIChannel)) + throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; + + let uri = aRequest.URI; + + let window = null; + let callbacks = aRequest.notificationCallbacks ? + aRequest.notificationCallbacks : + aRequest.loadGroup.notificationCallbacks; + if (callbacks) + window = callbacks.getInterface(Ci.nsIDOMWindow); + + aRequest.cancel(Cr.NS_BINDING_ABORTED); + + let installs = { + uris: [uri.spec], + hashes: [null], + names: [null], + icons: [null], + mimetype: XPI_CONTENT_TYPE, + triggeringPrincipal: aRequest.loadInfo.triggeringPrincipal, + callbackID: -1 + }; + + if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + // When running in the main process this might be a frame inside an + // in-content UI page, walk up to find the first frame element in a chrome + // privileged document + let element = window.frameElement; + let ssm = Services.scriptSecurityManager; + while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal)) + element = element.ownerDocument.defaultView.frameElement; + + if (element) { + let listener = Cc["@mozilla.org/addons/integration;1"]. + getService(Ci.nsIMessageListener); + listener.wrappedJSObject.receiveMessage({ + name: MSG_INSTALL_ADDONS, + target: element, + data: installs, + }); + return; + } + } + + // Fall back to sending through the message manager + let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + + messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, installs); + }, + + classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]), + + log : function(aMsg) { + let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg); + Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService). + logStringMessage(msg); + dump(msg + "\n"); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amContentHandler]); diff --git a/toolkit/mozapps/extensions/amIAddonManager.idl b/toolkit/mozapps/extensions/amIAddonManager.idl new file mode 100644 index 000000000..58a58b62d --- /dev/null +++ b/toolkit/mozapps/extensions/amIAddonManager.idl @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * A service to make some AddonManager functionality available to C++ callers. + * Javascript callers should still use AddonManager.jsm directly. + */ +[scriptable, function, uuid(7b45d82d-7ad5-48d7-9b05-f32eb9818cd4)] +interface amIAddonManager : nsISupports +{ + /** + * Synchronously map a URI to the corresponding Addon ID. + * + * Mappable URIs are limited to in-application resources belonging to the + * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. + * but do not include URIs from meta data, such as the add-on homepage. + * + * @param aURI + * The nsIURI to map + * @return + * true if the URI has been mapped successfully to an Addon ID + */ + boolean mapURIToAddonID(in nsIURI aURI, out AUTF8String aID); +}; diff --git a/toolkit/mozapps/extensions/amIAddonPathService.idl b/toolkit/mozapps/extensions/amIAddonPathService.idl new file mode 100644 index 000000000..9c9197a61 --- /dev/null +++ b/toolkit/mozapps/extensions/amIAddonPathService.idl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * This service maps file system paths where add-ons reside to the ID + * of the add-on. Paths are added by the add-on manager. They can + * looked up by anyone. + */ +[scriptable, uuid(fcd9e270-dfb1-11e3-8b68-0800200c9a66)] +interface amIAddonPathService : nsISupports +{ + /** + * Given a path to a file, return the ID of the add-on that the file belongs + * to. Returns an empty string if there is no add-on there. Note that if an + * add-on is located at /a/b/c, then looking up the path /a/b/c/d will return + * that add-on. + */ + AString findAddonId(in AString path); + + /** + * Call this function to inform the service that the given file system path is + * associated with the given add-on ID. + */ + void insertPath(in AString path, in AString addonId); + + /** + * Given a URI to a file, return the ID of the add-on that the file belongs + * to. Returns an empty string if there is no add-on there. + */ + AString mapURIToAddonId(in nsIURI aURI); +}; diff --git a/toolkit/mozapps/extensions/amIWebInstallListener.idl b/toolkit/mozapps/extensions/amIWebInstallListener.idl new file mode 100644 index 000000000..eed108097 --- /dev/null +++ b/toolkit/mozapps/extensions/amIWebInstallListener.idl @@ -0,0 +1,134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMElement; +interface nsIURI; +interface nsIVariant; + +/** + * amIWebInstallInfo is used by the default implementation of + * amIWebInstallListener to communicate with the running application and allow + * it to warn the user about blocked installs and start the installs running. + */ +[scriptable, uuid(fa0b47a3-f819-47ac-bc66-4bd1d7f67b1d)] +interface amIWebInstallInfo : nsISupports +{ + readonly attribute nsIDOMElement browser; + readonly attribute nsIURI originatingURI; + readonly attribute nsIVariant installs; + + /** + * Starts all installs. + */ + void install(); +}; + +/** + * The registered amIWebInstallListener is used to notify about new installs + * triggered by websites. The default implementation displays a confirmation + * dialog when add-ons are ready to install and uses the observer service to + * notify when installations are blocked. + */ +[scriptable, uuid(d9240d4b-6b3a-4cad-b402-de6c93337e0c)] +interface amIWebInstallListener : nsISupports +{ + /** + * Called when installation by websites is currently disabled. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were blocked + * @param aCount + * The number of AddonInstalls + */ + void onWebInstallDisabled(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); + + /** + * Called when the website is not allowed to directly prompt the user to + * install add-ons. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were blocked + * @param aCount + * The number of AddonInstalls + * @return true if the caller should start the installs + */ + boolean onWebInstallBlocked(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); + + /** + * Called when a website wants to ask the user to install add-ons. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were requested + * @param aCount + * The number of AddonInstalls + * @return true if the caller should start the installs + */ + boolean onWebInstallRequested(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); +}; + +[scriptable, uuid(a80b89ad-bb1a-4c43-9cb7-3ae656556f78)] +interface amIWebInstallListener2 : nsISupports +{ + /** + * Called when a non-same-origin resource attempted to initiate an install. + * Installs will have already been cancelled and cannot be restarted. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were blocked + * @param aCount + * The number of AddonInstalls + */ + boolean onWebInstallOriginBlocked(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); +}; + +/** + * amIWebInstallPrompt is used, if available, by the default implementation of + * amIWebInstallInfo to display a confirmation UI to the user before running + * installs. + */ +[scriptable, uuid(386906f1-4d18-45bf-bc81-5dcd68e42c3b)] +interface amIWebInstallPrompt : nsISupports +{ + /** + * Get a confirmation that the user wants to start the installs. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were requested + * @param aCount + * The number of AddonInstalls + */ + void confirm(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); +}; diff --git a/toolkit/mozapps/extensions/amIWebInstaller.idl b/toolkit/mozapps/extensions/amIWebInstaller.idl new file mode 100644 index 000000000..6c5ebca67 --- /dev/null +++ b/toolkit/mozapps/extensions/amIWebInstaller.idl @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMElement; +interface nsIVariant; +interface nsIURI; + +/** + * A callback function used to notify webpages when a requested install has + * ended. + * + * NOTE: This is *not* the same as InstallListener. + */ +[scriptable, function, uuid(bb22f5c0-3ca1-48f6-873c-54e87987700f)] +interface amIInstallCallback : nsISupports +{ + /** + * Called when an install completes or fails. + * + * @param aUrl + * The url of the add-on being installed + * @param aStatus + * 0 if the install was successful or negative if not + */ + void onInstallEnded(in AString aUrl, in int32_t aStatus); +}; + + +/** + * This interface is used to allow webpages to start installing add-ons. + */ +[scriptable, uuid(658d6c09-15e0-4688-bee8-8551030472a9)] +interface amIWebInstaller : nsISupports +{ + /** + * Checks if installation is enabled for a webpage. + * + * @param aMimetype + * The mimetype for the add-on to be installed + * @param referer + * The URL of the webpage trying to install an add-on + * @return true if installation is enabled + */ + boolean isInstallEnabled(in AString aMimetype, in nsIURI aReferer); + + /** + * Installs an array of add-ons at the request of a webpage + * + * @param aMimetype + * The mimetype for the add-ons + * @param aBrowser + * The browser installing the add-ons. + * @param aReferer + * The URI for the webpage installing the add-ons + * @param aUris + * The URIs of add-ons to be installed + * @param aHashes + * The hashes for the add-ons to be installed + * @param aNames + * The names for the add-ons to be installed + * @param aIcons + * The icons for the add-ons to be installed + * @param aCallback + * An optional callback to notify about installation success and + * failure + * @param aInstallCount + * An optional argument including the number of add-ons to install + * @return true if the installation was successfully started + */ + boolean installAddonsFromWebpage(in AString aMimetype, + in nsIDOMElement aBrowser, + in nsIURI aReferer, + [array, size_is(aInstallCount)] in wstring aUris, + [array, size_is(aInstallCount)] in wstring aHashes, + [array, size_is(aInstallCount)] in wstring aNames, + [array, size_is(aInstallCount)] in wstring aIcons, + [optional] in amIInstallCallback aCallback, + [optional] in uint32_t aInstallCount); +}; diff --git a/toolkit/mozapps/extensions/amInstallTrigger.js b/toolkit/mozapps/extensions/amInstallTrigger.js new file mode 100644 index 000000000..382791d32 --- /dev/null +++ b/toolkit/mozapps/extensions/amInstallTrigger.js @@ -0,0 +1,240 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/Log.jsm"); + +const XPINSTALL_MIMETYPE = "application/x-xpinstall"; + +const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; +const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; +const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; + + +var log = Log.repository.getLogger("AddonManager.InstallTrigger"); +log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"]; + +function CallbackObject(id, callback, urls, mediator) { + this.id = id; + this.callback = callback; + this.urls = new Set(urls); + this.callCallback = function(url, status) { + try { + this.callback(url, status); + } + catch (e) { + log.warn("InstallTrigger callback threw an exception: " + e); + } + + this.urls.delete(url); + if (this.urls.size == 0) + mediator._callbacks.delete(id); + }; +} + +function RemoteMediator(window) { + window.QueryInterface(Ci.nsIInterfaceRequestor); + let utils = window.getInterface(Ci.nsIDOMWindowUtils); + this._windowID = utils.currentInnerWindowID; + + this.mm = window + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this); + + this._lastCallbackID = 0; + this._callbacks = new Map(); +} + +RemoteMediator.prototype = { + receiveMessage: function(message) { + if (message.name == MSG_INSTALL_CALLBACK) { + let payload = message.data; + let callbackHandler = this._callbacks.get(payload.callbackID); + if (callbackHandler) { + callbackHandler.callCallback(payload.url, payload.status); + } + } + }, + + enabled: function(url) { + let params = { + mimetype: XPINSTALL_MIMETYPE + }; + return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0]; + }, + + install: function(installs, principal, callback, window) { + let callbackID = this._addCallback(callback, installs.uris); + + installs.mimetype = XPINSTALL_MIMETYPE; + installs.triggeringPrincipal = principal; + installs.callbackID = callbackID; + + if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + // When running in the main process this might be a frame inside an + // in-content UI page, walk up to find the first frame element in a chrome + // privileged document + let element = window.frameElement; + let ssm = Services.scriptSecurityManager; + while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal)) + element = element.ownerDocument.defaultView.frameElement; + + if (element) { + let listener = Cc["@mozilla.org/addons/integration;1"]. + getService(Ci.nsIMessageListener); + return listener.wrappedJSObject.receiveMessage({ + name: MSG_INSTALL_ADDONS, + target: element, + data: installs, + }); + } + } + + // Fall back to sending through the message manager + let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + + return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0]; + }, + + _addCallback: function(callback, urls) { + if (!callback || typeof callback != "function") + return -1; + + let callbackID = this._windowID + "-" + ++this._lastCallbackID; + let callbackObject = new CallbackObject(callbackID, callback, urls, this); + this._callbacks.set(callbackID, callbackObject); + return callbackID; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]) +}; + + +function InstallTrigger() { +} + +InstallTrigger.prototype = { + // Here be magic. We've declared ourselves as providing the + // nsIDOMGlobalPropertyInitializer interface, and are registered in the + // "JavaScript-global-property" category in the XPCOM category manager. This + // means that for newly created windows, XPCOM will createinstance this + // object, and then call init, passing in the window for which we need to + // provide an instance. We then initialize ourselves and return the webidl + // version of this object using the webidl-provided _create method, which + // XPCOM will then duly expose as a property value on the window. All this + // indirection is necessary because webidl does not (yet) support statics + // (bug 863952). See bug 926712 for more details about this implementation. + init: function(window) { + this._window = window; + this._principal = window.document.nodePrincipal; + this._url = window.document.documentURIObject; + + try { + this._mediator = new RemoteMediator(window); + } catch (ex) { + // If we can't set up IPC (e.g., because this is a top-level window + // or something), then don't expose InstallTrigger. + return null; + } + + return window.InstallTriggerImpl._create(window, this); + }, + + enabled: function() { + return this._mediator.enabled(this._url.spec); + }, + + updateEnabled: function() { + return this.enabled(); + }, + + install: function(installs, callback) { + let installData = { + uris: [], + hashes: [], + names: [], + icons: [], + }; + + for (let name of Object.keys(installs)) { + let item = installs[name]; + if (typeof item === "string") { + item = { URL: item }; + } + if (!item.URL) { + throw new this._window.Error("Missing URL property for '" + name + "'"); + } + + let url = this._resolveURL(item.URL); + if (!this._checkLoadURIFromScript(url)) { + throw new this._window.Error("Insufficient permissions to install: " + url.spec); + } + + let iconUrl = null; + if (item.IconURL) { + iconUrl = this._resolveURL(item.IconURL); + if (!this._checkLoadURIFromScript(iconUrl)) { + iconUrl = null; // If page can't load the icon, just ignore it + } + } + + installData.uris.push(url.spec); + installData.hashes.push(item.Hash || null); + installData.names.push(name); + installData.icons.push(iconUrl ? iconUrl.spec : null); + } + + return this._mediator.install(installData, this._principal, callback, this._window); + }, + + startSoftwareUpdate: function(url, flags) { + let filename = Services.io.newURI(url, null, null) + .QueryInterface(Ci.nsIURL) + .filename; + let args = {}; + args[filename] = { "URL": url }; + return this.install(args); + }, + + installChrome: function(type, url, skin) { + return this.startSoftwareUpdate(url); + }, + + _resolveURL: function(url) { + return Services.io.newURI(url, null, this._url); + }, + + _checkLoadURIFromScript: function(uri) { + let secman = Services.scriptSecurityManager; + try { + secman.checkLoadURIWithPrincipal(this._principal, + uri, + secman.DISALLOW_INHERIT_PRINCIPAL); + return true; + } + catch (e) { + return false; + } + }, + + classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"), + contractID: "@mozilla.org/addons/installtrigger;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer]) +}; + + + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]); diff --git a/toolkit/mozapps/extensions/amWebAPI.js b/toolkit/mozapps/extensions/amWebAPI.js new file mode 100644 index 000000000..5ad0d23f1 --- /dev/null +++ b/toolkit/mozapps/extensions/amWebAPI.js @@ -0,0 +1,269 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; +const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; +const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; +const MSG_INSTALL_CLEANUP = "WebAPICleanup"; +const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; +const MSG_ADDON_EVENT = "WebAPIAddonEvent"; + +class APIBroker { + constructor(mm) { + this.mm = mm; + + this._promises = new Map(); + + // _installMap maps integer ids to DOM AddonInstall instances + this._installMap = new Map(); + + this.mm.addMessageListener(MSG_PROMISE_RESULT, this); + this.mm.addMessageListener(MSG_INSTALL_EVENT, this); + + this._eventListener = null; + } + + receiveMessage(message) { + let payload = message.data; + + switch (message.name) { + case MSG_PROMISE_RESULT: { + if (!this._promises.has(payload.callbackID)) { + return; + } + + let resolve = this._promises.get(payload.callbackID); + this._promises.delete(payload.callbackID); + resolve(payload); + break; + } + + case MSG_INSTALL_EVENT: { + let install = this._installMap.get(payload.id); + if (!install) { + let err = new Error(`Got install event for unknown install ${payload.id}`); + Cu.reportError(err); + return; + } + install._dispatch(payload); + break; + } + + case MSG_ADDON_EVENT: { + if (this._eventListener) { + this._eventListener(payload); + } + } + } + } + + sendRequest(type, ...args) { + return new Promise(resolve => { + let callbackID = APIBroker._nextID++; + + this._promises.set(callbackID, resolve); + this.mm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args }); + }); + } + + setAddonListener(callback) { + this._eventListener = callback; + if (callback) { + this.mm.addMessageListener(MSG_ADDON_EVENT, this); + this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: true}); + } else { + this.mm.removeMessageListener(MSG_ADDON_EVENT, this); + this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false}); + } + } + + sendCleanup(ids) { + this.setAddonListener(null); + this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids }); + } +} + +APIBroker._nextID = 0; + +// Base class for building classes to back content-exposed interfaces. +class APIObject { + init(window, broker, properties) { + this.window = window; + this.broker = broker; + + // Copy any provided properties onto this object, webidl bindings + // will only expose to content what should be exposed. + for (let key of Object.keys(properties)) { + this[key] = properties[key]; + } + } + + /** + * Helper to implement an asychronous method visible to content, where + * the method is implemented by sending a message to the parent process + * and then wrapping the returned object or error in an appropriate object. + * This helper method ensures that: + * - Returned Promise objects are from the content window + * - Rejected Promises have Error objects from the content window + * - Only non-internal errors are exposed to the caller + * + * @param {string} apiRequest The command to invoke in the parent process. + * @param {array} apiArgs The arguments to include with the + * request to the parent process. + * @param {function} resultConvert If provided, a function called with the + * result from the parent process as an + * argument. Used to convert the result + * into something appropriate for content. + * @returns {Promise} A Promise suitable for passing directly to content. + */ + _apiTask(apiRequest, apiArgs, resultConverter) { + let win = this.window; + let broker = this.broker; + return new win.Promise((resolve, reject) => { + Task.spawn(function*() { + let result = yield broker.sendRequest(apiRequest, ...apiArgs); + if ("reject" in result) { + let err = new win.Error(result.reject.message); + // We don't currently put any other properties onto Errors + // generated by mozAddonManager. If/when we do, they will + // need to get copied here. + reject(err); + return; + } + + let obj = result.resolve; + if (resultConverter) { + obj = resultConverter(obj); + } + resolve(obj); + }).catch(err => { + Cu.reportError(err); + reject(new win.Error("Unexpected internal error")); + }); + }); + } +} + +class Addon extends APIObject { + constructor(...args) { + super(); + this.init(...args); + } + + uninstall() { + return this._apiTask("addonUninstall", [this.id]); + } + + setEnabled(value) { + return this._apiTask("addonSetEnabled", [this.id, value]); + } +} + +class AddonInstall extends APIObject { + constructor(window, broker, properties) { + super(); + this.init(window, broker, properties); + + broker._installMap.set(properties.id, this); + } + + _dispatch(data) { + // The message for the event includes updated copies of all install + // properties. Use the usual "let webidl filter visible properties" trick. + for (let key of Object.keys(data)) { + this[key] = data[key]; + } + + let event = new this.window.Event(data.event); + this.__DOM_IMPL__.dispatchEvent(event); + } + + install() { + return this._apiTask("addonInstallDoInstall", [this.id]); + } + + cancel() { + return this._apiTask("addonInstallCancel", [this.id]); + } +} + +class WebAPI extends APIObject { + constructor() { + super(); + this.allInstalls = []; + this.listenerCount = 0; + } + + init(window) { + let mm = window + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + let broker = new APIBroker(mm); + + super.init(window, broker, {}); + + window.addEventListener("unload", event => { + this.broker.sendCleanup(this.allInstalls); + }); + } + + getAddonByID(id) { + return this._apiTask("getAddonByID", [id], addonInfo => { + if (!addonInfo) { + return null; + } + let addon = new Addon(this.window, this.broker, addonInfo); + return this.window.Addon._create(this.window, addon); + }); + } + + createInstall(options) { + return this._apiTask("createInstall", [options], installInfo => { + if (!installInfo) { + return null; + } + let install = new AddonInstall(this.window, this.broker, installInfo); + this.allInstalls.push(installInfo.id); + return this.window.AddonInstall._create(this.window, install); + }); + } + + eventListenerWasAdded(type) { + if (this.listenerCount == 0) { + this.broker.setAddonListener(data => { + let event = new this.window.AddonEvent(data.event, data); + this.__DOM_IMPL__.dispatchEvent(event); + }); + } + this.listenerCount++; + } + + eventListenerWasRemoved(type) { + this.listenerCount--; + if (this.listenerCount == 0) { + this.broker.setAddonListener(null); + } + } + + QueryInterface(iid) { + if (iid.equals(WebAPI.classID) || iid.equals(Ci.nsISupports) + || iid.equals(Ci.nsIDOMGlobalPropertyInitializer)) { + return this; + } + return Cr.NS_ERROR_NO_INTERFACE; + } +} + +WebAPI.prototype.classID = Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"); +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]); diff --git a/toolkit/mozapps/extensions/amWebInstallListener.js b/toolkit/mozapps/extensions/amWebInstallListener.js new file mode 100644 index 000000000..0bcc345e8 --- /dev/null +++ b/toolkit/mozapps/extensions/amWebInstallListener.js @@ -0,0 +1,348 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This is a default implementation of amIWebInstallListener that should work + * for most applications but can be overriden. It notifies the observer service + * about blocked installs. For normal installs it pops up an install + * confirmation when all the add-ons have been downloaded. + */ + +"use strict"; + +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/AddonManager.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Preferences.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm"); + +const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; + +// Installation can begin from any of these states +const READY_STATES = [ + AddonManager.STATE_AVAILABLE, + AddonManager.STATE_DOWNLOAD_FAILED, + AddonManager.STATE_INSTALL_FAILED, + AddonManager.STATE_CANCELLED +]; + +Cu.import("resource://gre/modules/Log.jsm"); +const LOGGER_ID = "addons.weblistener"; + +// Create a new logger for use by the Addons Web Listener +// (Requires AddonManager.jsm) +var logger = Log.repository.getLogger(LOGGER_ID); + +function notifyObservers(aTopic, aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, aTopic, null); +} + +/** + * Creates a new installer to monitor downloads and prompt to install when + * ready + * + * @param aBrowser + * The browser that started the installations + * @param aUrl + * The URL that started the installations + * @param aInstalls + * An array of AddonInstalls + */ +function Installer(aBrowser, aUrl, aInstalls) { + this.browser = aBrowser; + this.url = aUrl; + this.downloads = aInstalls; + this.installed = []; + + notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls); + + for (let install of aInstalls) { + install.addListener(this); + + // Start downloading if it hasn't already begun + if (READY_STATES.indexOf(install.state) != -1) + install.install(); + } + + this.checkAllDownloaded(); +} + +Installer.prototype = { + browser: null, + downloads: null, + installed: null, + isDownloading: true, + + /** + * Checks if all downloads are now complete and if so prompts to install. + */ + checkAllDownloaded: function() { + // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted + // installs. + if (!this.isDownloading) + return; + + var failed = []; + var installs = []; + + for (let install of this.downloads) { + switch (install.state) { + case AddonManager.STATE_AVAILABLE: + case AddonManager.STATE_DOWNLOADING: + // Exit early if any add-ons haven't started downloading yet or are + // still downloading + return; + case AddonManager.STATE_DOWNLOAD_FAILED: + failed.push(install); + break; + case AddonManager.STATE_DOWNLOADED: + // App disabled items are not compatible and so fail to install + if (install.addon.appDisabled) + failed.push(install); + else + installs.push(install); + + if (install.linkedInstalls) { + for (let linkedInstall of install.linkedInstalls) { + linkedInstall.addListener(this); + // Corrupt or incompatible items fail to install + if (linkedInstall.state == AddonManager.STATE_DOWNLOAD_FAILED || linkedInstall.addon.appDisabled) + failed.push(linkedInstall); + else + installs.push(linkedInstall); + } + } + break; + case AddonManager.STATE_CANCELLED: + // Just ignore cancelled downloads + break; + default: + logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " + + install.state); + } + } + + this.isDownloading = false; + this.downloads = installs; + + if (failed.length > 0) { + // Stop listening and cancel any installs that are failed because of + // compatibility reasons. + for (let install of failed) { + if (install.state == AddonManager.STATE_DOWNLOADED) { + install.removeListener(this); + install.cancel(); + } + } + notifyObservers("addon-install-failed", this.browser, this.url, failed); + } + + // If none of the downloads were successful then exit early + if (this.downloads.length == 0) + return; + + // Check for a custom installation prompt that may be provided by the + // applicaton + if ("@mozilla.org/addons/web-install-prompt;1" in Cc) { + try { + let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"]. + getService(Ci.amIWebInstallPrompt); + prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length); + return; + } + catch (e) {} + } + + if (Preferences.get("xpinstall.customConfirmationUI", false)) { + notifyObservers("addon-install-confirmation", this.browser, this.url, this.downloads); + return; + } + + let args = {}; + args.url = this.url; + args.installs = this.downloads; + args.wrappedJSObject = args; + + try { + Cc["@mozilla.org/base/telemetry;1"]. + getService(Ci.nsITelemetry). + getHistogramById("SECURITY_UI"). + add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL); + let parentWindow = null; + if (this.browser) { + parentWindow = this.browser.ownerDocument.defaultView; + PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser); + } + Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG, + null, "chrome,modal,centerscreen", args); + } catch (e) { + logger.warn("Exception showing install confirmation dialog", e); + for (let install of this.downloads) { + install.removeListener(this); + // Cancel the installs, as currently there is no way to make them fail + // from here. + install.cancel(); + } + notifyObservers("addon-install-cancelled", this.browser, this.url, + this.downloads); + } + }, + + /** + * Checks if all installs are now complete and if so notifies observers. + */ + checkAllInstalled: function() { + var failed = []; + + for (let install of this.downloads) { + switch (install.state) { + case AddonManager.STATE_DOWNLOADED: + case AddonManager.STATE_INSTALLING: + // Exit early if any add-ons haven't started installing yet or are + // still installing + return; + case AddonManager.STATE_INSTALL_FAILED: + failed.push(install); + break; + } + } + + this.downloads = null; + + if (failed.length > 0) + notifyObservers("addon-install-failed", this.browser, this.url, failed); + + if (this.installed.length > 0) + notifyObservers("addon-install-complete", this.browser, this.url, this.installed); + this.installed = null; + }, + + onDownloadCancelled: function(aInstall) { + aInstall.removeListener(this); + this.checkAllDownloaded(); + }, + + onDownloadFailed: function(aInstall) { + aInstall.removeListener(this); + this.checkAllDownloaded(); + }, + + onDownloadEnded: function(aInstall) { + this.checkAllDownloaded(); + return false; + }, + + onInstallCancelled: function(aInstall) { + aInstall.removeListener(this); + this.checkAllInstalled(); + }, + + onInstallFailed: function(aInstall) { + aInstall.removeListener(this); + this.checkAllInstalled(); + }, + + onInstallEnded: function(aInstall) { + aInstall.removeListener(this); + this.installed.push(aInstall); + + // If installing a theme that is disabled and can be enabled then enable it + if (aInstall.addon.type == "theme" && + aInstall.addon.userDisabled == true && + aInstall.addon.appDisabled == false) { + aInstall.addon.userDisabled = false; + } + + this.checkAllInstalled(); + } +}; + +function extWebInstallListener() { +} + +extWebInstallListener.prototype = { + /** + * @see amIWebInstallListener.idl + */ + onWebInstallDisabled: function(aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, "addon-install-disabled", null); + }, + + /** + * @see amIWebInstallListener.idl + */ + onWebInstallOriginBlocked: function(aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + install: function() { + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, "addon-install-origin-blocked", null); + + return false; + }, + + /** + * @see amIWebInstallListener.idl + */ + onWebInstallBlocked: function(aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + install: function() { + new Installer(this.browser, this.originatingURI, this.installs); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, "addon-install-blocked", null); + + return false; + }, + + /** + * @see amIWebInstallListener.idl + */ + onWebInstallRequested: function(aBrowser, aUri, aInstalls) { + new Installer(aBrowser, aUri, aInstalls); + + // We start the installs ourself + return false; + }, + + classDescription: "XPI Install Handler", + contractID: "@mozilla.org/addons/web-install-listener;1", + classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"), + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener, + Ci.amIWebInstallListener2]) +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]); diff --git a/toolkit/mozapps/extensions/content/OpenH264-license.txt b/toolkit/mozapps/extensions/content/OpenH264-license.txt new file mode 100644 index 000000000..ad37989b8 --- /dev/null +++ b/toolkit/mozapps/extensions/content/OpenH264-license.txt @@ -0,0 +1,59 @@ +------------------------------------------------------- +About The Cisco-Provided Binary of OpenH264 Video Codec +------------------------------------------------------- + +Cisco provides this program under the terms of the BSD license. + +Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met. + +As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice. + +For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary + +A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org + +----------- +BSD License +----------- + +Copyright © 2014 Cisco Systems, Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS†AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------- +AVC/H.264 Patent Portfolio License Notice +----------------------------------------- + +The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software: + +THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEOâ€) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM + +Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla + +--------------------------------------------- +AVC/H.264 Patent Portfolio License Conditions +--------------------------------------------- + +In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met: + +1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device; + +2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary; + +3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text: + + "OpenH264 Video Codec provided by Cisco Systems, Inc." + +4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user. + + + + v1.0 diff --git a/toolkit/mozapps/extensions/content/about.js b/toolkit/mozapps/extensions/content/about.js new file mode 100644 index 000000000..4f8fb353e --- /dev/null +++ b/toolkit/mozapps/extensions/content/about.js @@ -0,0 +1,103 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* import-globals-from ../../../content/contentAreaUtils.js */ + +var Cu = Components.utils; +Cu.import("resource://gre/modules/AddonManager.jsm"); + +function init() { + var addon = window.arguments[0]; + var extensionsStrings = document.getElementById("extensionsStrings"); + + document.documentElement.setAttribute("addontype", addon.type); + + var iconURL = AddonManager.getPreferredIconURL(addon, 48, window); + if (iconURL) { + var extensionIcon = document.getElementById("extensionIcon"); + extensionIcon.src = iconURL; + } + + document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]); + var extensionName = document.getElementById("extensionName"); + extensionName.textContent = addon.name; + + var extensionVersion = document.getElementById("extensionVersion"); + if (addon.version) + extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version])); + else + extensionVersion.hidden = true; + + var extensionDescription = document.getElementById("extensionDescription"); + if (addon.description) + extensionDescription.textContent = addon.description; + else + extensionDescription.hidden = true; + + var numDetails = 0; + + var extensionCreator = document.getElementById("extensionCreator"); + if (addon.creator) { + extensionCreator.setAttribute("value", addon.creator); + numDetails++; + } else { + extensionCreator.hidden = true; + var extensionCreatorLabel = document.getElementById("extensionCreatorLabel"); + extensionCreatorLabel.hidden = true; + } + + var extensionHomepage = document.getElementById("extensionHomepage"); + var homepageURL = addon.homepageURL; + if (homepageURL) { + extensionHomepage.setAttribute("homepageURL", homepageURL); + extensionHomepage.setAttribute("tooltiptext", homepageURL); + numDetails++; + } else { + extensionHomepage.hidden = true; + } + + numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers); + numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators); + numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors); + + if (numDetails == 0) { + var groove = document.getElementById("groove"); + groove.hidden = true; + var extensionDetailsBox = document.getElementById("extensionDetailsBox"); + extensionDetailsBox.hidden = true; + } + + var acceptButton = document.documentElement.getButton("accept"); + acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton"); + + setTimeout(sizeToContent, 0); +} + +function appendToList(aHeaderId, aNodeId, aItems) { + var header = document.getElementById(aHeaderId); + var node = document.getElementById(aNodeId); + + if (!aItems || aItems.length == 0) { + header.hidden = true; + return 0; + } + + for (let currentItem of aItems) { + var label = document.createElement("label"); + label.textContent = currentItem; + label.setAttribute("class", "contributor"); + node.appendChild(label); + } + + return aItems.length; +} + +function loadHomepage(aEvent) { + window.close(); + openURL(aEvent.target.getAttribute("homepageURL")); +} diff --git a/toolkit/mozapps/extensions/content/about.xul b/toolkit/mozapps/extensions/content/about.xul new file mode 100644 index 000000000..6effcf37a --- /dev/null +++ b/toolkit/mozapps/extensions/content/about.xul @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + +

Test page for the discovery pane

+

Direct install

+

JS install

+ + diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js new file mode 100644 index 000000000..5a749099d --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -0,0 +1,1468 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +/* globals end_test*/ + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +var tmp = {}; +Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp); +Components.utils.import("resource://gre/modules/Log.jsm", tmp); +var AddonManager = tmp.AddonManager; +var AddonManagerPrivate = tmp.AddonManagerPrivate; +var Log = tmp.Log; + +var pathParts = gTestPath.split("/"); +// Drop the test filename +pathParts.splice(pathParts.length - 1, pathParts.length); + +var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]); + +// Drop the UI type +if (gTestInWindow) { + pathParts.splice(pathParts.length - 1, pathParts.length); +} + +const RELATIVE_DIR = pathParts.slice(4).join("/") + "/"; + +const TESTROOT = "http://example.com/" + RELATIVE_DIR; +const SECURE_TESTROOT = "https://example.com/" + RELATIVE_DIR; +const TESTROOT2 = "http://example.org/" + RELATIVE_DIR; +const SECURE_TESTROOT2 = "https://example.org/" + RELATIVE_DIR; +const CHROMEROOT = pathParts.join("/") + "/"; +const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; +const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; +const PREF_XPI_ENABLED = "xpinstall.enabled"; +const PREF_UPDATEURL = "extensions.update.url"; +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; +const PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI = "xpinstall.customConfirmationUI"; +const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory"; + +const MANAGER_URI = "about:addons"; +const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults"; +const PREF_STRICT_COMPAT = "extensions.strictCompatibility"; + +var PREF_CHECK_COMPATIBILITY; +(function() { + var channel = "default"; + try { + channel = Services.prefs.getCharPref("app.update.channel"); + } catch (e) { } + if (channel != "aurora" && + channel != "beta" && + channel != "release" && + channel != "esr") { + var version = "nightly"; + } else { + version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); + } + PREF_CHECK_COMPATIBILITY = "extensions.checkCompatibility." + version; +})(); + +var gPendingTests = []; +var gTestsRun = 0; +var gTestStart = null; + +var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window); + +var gRestorePrefs = [{name: PREF_LOGGING_ENABLED}, + {name: PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI}, + {name: "extensions.webservice.discoverURL"}, + {name: "extensions.update.url"}, + {name: "extensions.update.background.url"}, + {name: "extensions.update.enabled"}, + {name: "extensions.update.autoUpdateDefault"}, + {name: "extensions.getAddons.get.url"}, + {name: "extensions.getAddons.getWithPerformance.url"}, + {name: "extensions.getAddons.search.browseURL"}, + {name: "extensions.getAddons.search.url"}, + {name: "extensions.getAddons.cache.enabled"}, + {name: "devtools.chrome.enabled"}, + {name: PREF_SEARCH_MAXRESULTS}, + {name: PREF_STRICT_COMPAT}, + {name: PREF_CHECK_COMPATIBILITY}]; + +for (let pref of gRestorePrefs) { + if (!Services.prefs.prefHasUserValue(pref.name)) { + pref.type = "clear"; + continue; + } + pref.type = Services.prefs.getPrefType(pref.name); + if (pref.type == Services.prefs.PREF_BOOL) + pref.value = Services.prefs.getBoolPref(pref.name); + else if (pref.type == Services.prefs.PREF_INT) + pref.value = Services.prefs.getIntPref(pref.name); + else if (pref.type == Services.prefs.PREF_STRING) + pref.value = Services.prefs.getCharPref(pref.name); +} + +// Turn logging on for all tests +Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); + +Services.prefs.setBoolPref(PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI, false); + +// Helper to register test failures and close windows if any are left open +function checkOpenWindows(aWindowID) { + let windows = Services.wm.getEnumerator(aWindowID); + let found = false; + while (windows.hasMoreElements()) { + let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow); + if (!win.closed) { + found = true; + win.close(); + } + } + if (found) + ok(false, "Found unexpected " + aWindowID + " window still open"); +} + +// Tools to disable and re-enable the background update and blocklist timers +// so that tests can protect themselves from unwanted timer events. +var gCatMan = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); +// Default values from toolkit/mozapps/extensions/extensions.manifest, but disable*UpdateTimer() +// records the actual value so we can put it back in enable*UpdateTimer() +var backgroundUpdateConfig = "@mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400"; +var blocklistUpdateConfig = "@mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400"; + +var UTIMER = "update-timer"; +var AMANAGER = "addonManager"; +var BLOCKLIST = "nsBlocklistService"; + +function disableBackgroundUpdateTimer() { + info("Disabling " + UTIMER + " " + AMANAGER); + backgroundUpdateConfig = gCatMan.getCategoryEntry(UTIMER, AMANAGER); + gCatMan.deleteCategoryEntry(UTIMER, AMANAGER, true); +} + +function enableBackgroundUpdateTimer() { + info("Enabling " + UTIMER + " " + AMANAGER); + gCatMan.addCategoryEntry(UTIMER, AMANAGER, backgroundUpdateConfig, false, true); +} + +function disableBlocklistUpdateTimer() { + info("Disabling " + UTIMER + " " + BLOCKLIST); + blocklistUpdateConfig = gCatMan.getCategoryEntry(UTIMER, BLOCKLIST); + gCatMan.deleteCategoryEntry(UTIMER, BLOCKLIST, true); +} + +function enableBlocklistUpdateTimer() { + info("Enabling " + UTIMER + " " + BLOCKLIST); + gCatMan.addCategoryEntry(UTIMER, BLOCKLIST, blocklistUpdateConfig, false, true); +} + +registerCleanupFunction(function() { + // Restore prefs + for (let pref of gRestorePrefs) { + if (pref.type == "clear") + Services.prefs.clearUserPref(pref.name); + else if (pref.type == Services.prefs.PREF_BOOL) + Services.prefs.setBoolPref(pref.name, pref.value); + else if (pref.type == Services.prefs.PREF_INT) + Services.prefs.setIntPref(pref.name, pref.value); + else if (pref.type == Services.prefs.PREF_STRING) + Services.prefs.setCharPref(pref.name, pref.value); + } + + // Throw an error if the add-ons manager window is open anywhere + checkOpenWindows("Addons:Manager"); + checkOpenWindows("Addons:Compatibility"); + checkOpenWindows("Addons:Install"); + + return new Promise((resolve, reject) => AddonManager.getAllInstalls(resolve)) + .then(aInstalls => { + for (let install of aInstalls) { + if (install instanceof MockInstall) + continue; + + ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state); + install.cancel(); + } + }); +}); + +function log_exceptions(aCallback, ...aArgs) { + try { + return aCallback.apply(null, aArgs); + } + catch (e) { + info("Exception thrown: " + e); + throw e; + } +} + +function log_callback(aPromise, aCallback) { + aPromise.then(aCallback) + .then(null, e => info("Exception thrown: " + e)); + return aPromise; +} + +function add_test(test) { + gPendingTests.push(test); +} + +function run_next_test() { + // Make sure we're not calling run_next_test from inside an add_task() test + // We're inside the browser_test.js 'testScope' here + if (this.__tasks) { + throw new Error("run_next_test() called from an add_task() test function. " + + "run_next_test() should not be called from inside add_task() " + + "under any circumstances!"); + } + if (gTestsRun > 0) + info("Test " + gTestsRun + " took " + (Date.now() - gTestStart) + "ms"); + + if (gPendingTests.length == 0) { + executeSoon(end_test); + return; + } + + gTestsRun++; + var test = gPendingTests.shift(); + if (test.name) + info("Running test " + gTestsRun + " (" + test.name + ")"); + else + info("Running test " + gTestsRun); + + gTestStart = Date.now(); + executeSoon(() => log_exceptions(test)); +} + +var get_tooltip_info = Task.async(function*(addon) { + let managerWindow = addon.ownerDocument.defaultView; + + // The popup code uses a triggering event's target to set the + // document.tooltipNode property. + let nameNode = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "name"); + let event = new managerWindow.CustomEvent("TriggerEvent"); + nameNode.dispatchEvent(event); + + let tooltip = managerWindow.document.getElementById("addonitem-tooltip"); + + let promise = BrowserTestUtils.waitForEvent(tooltip, "popupshown"); + tooltip.openPopup(nameNode, "after_start", 0, 0, false, false, event); + yield promise; + + let tiptext = tooltip.label; + + promise = BrowserTestUtils.waitForEvent(tooltip, "popuphidden"); + tooltip.hidePopup(); + yield promise; + + let expectedName = addon.getAttribute("name"); + ok(tiptext.substring(0, expectedName.length), expectedName, + "Tooltip should always start with the expected name"); + + if (expectedName.length == tiptext.length) { + return { + name: tiptext, + version: undefined + }; + } + return { + name: tiptext.substring(0, expectedName.length), + version: tiptext.substring(expectedName.length + 1) + }; +}); + +function get_addon_file_url(aFilename) { + try { + var cr = Cc["@mozilla.org/chrome/chrome-registry;1"]. + getService(Ci.nsIChromeRegistry); + var fileurl = cr.convertChromeURL(makeURI(CHROMEROOT + "addons/" + aFilename)); + return fileurl.QueryInterface(Ci.nsIFileURL); + } catch (ex) { + var jar = getJar(CHROMEROOT + "addons/" + aFilename); + var tmpDir = extractJarToTmp(jar); + tmpDir.append(aFilename); + + return Services.io.newFileURI(tmpDir).QueryInterface(Ci.nsIFileURL); + } +} + +function get_current_view(aManager) { + let view = aManager.document.getElementById("view-port").selectedPanel; + if (view.id == "headered-views") { + view = aManager.document.getElementById("headered-views-content").selectedPanel; + } + is(view, aManager.gViewController.displayedView, "view controller is tracking the displayed view correctly"); + return view; +} + +function get_test_items_in_list(aManager) { + var tests = "@tests.mozilla.org"; + + let view = get_current_view(aManager); + let listid = view.id == "search-view" ? "search-list" : "addon-list"; + let item = aManager.document.getElementById(listid).firstChild; + let items = []; + + while (item) { + if (item.localName != "richlistitem") { + item = item.nextSibling; + continue; + } + + if (!item.mAddon || item.mAddon.id.substring(item.mAddon.id.length - tests.length) == tests) + items.push(item); + item = item.nextSibling; + } + + return items; +} + +function check_all_in_list(aManager, aIds, aIgnoreExtras) { + var doc = aManager.document; + var view = get_current_view(aManager); + var listid = view.id == "search-view" ? "search-list" : "addon-list"; + var list = doc.getElementById(listid); + + var inlist = []; + var node = list.firstChild; + while (node) { + if (node.value) + inlist.push(node.value); + node = node.nextSibling; + } + + for (let id of aIds) { + if (inlist.indexOf(id) == -1) + ok(false, "Should find " + id + " in the list"); + } + + if (aIgnoreExtras) + return; + + for (let inlistItem of inlist) { + if (aIds.indexOf(inlistItem) == -1) + ok(false, "Shouldn't have seen " + inlistItem + " in the list"); + } +} + +function get_addon_element(aManager, aId) { + var doc = aManager.document; + var view = get_current_view(aManager); + var listid = "addon-list"; + if (view.id == "search-view") + listid = "search-list"; + else if (view.id == "updates-view") + listid = "updates-list"; + var list = doc.getElementById(listid); + + var node = list.firstChild; + while (node) { + if (node.value == aId) + return node; + node = node.nextSibling; + } + return null; +} + +function wait_for_view_load(aManagerWindow, aCallback, aForceWait, aLongerTimeout) { + requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); + + if (!aForceWait && !aManagerWindow.gViewController.isLoading) { + log_exceptions(aCallback, aManagerWindow); + return; + } + + aManagerWindow.document.addEventListener("ViewChanged", function() { + aManagerWindow.document.removeEventListener("ViewChanged", arguments.callee, false); + log_exceptions(aCallback, aManagerWindow); + }, false); +} + +function wait_for_manager_load(aManagerWindow, aCallback) { + if (!aManagerWindow.gIsInitializing) { + log_exceptions(aCallback, aManagerWindow); + return; + } + + info("Waiting for initialization"); + aManagerWindow.document.addEventListener("Initialized", function() { + aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false); + log_exceptions(aCallback, aManagerWindow); + }, false); +} + +function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) { + let p = new Promise((resolve, reject) => { + + function setup_manager(aManagerWindow) { + if (aLoadCallback) + log_exceptions(aLoadCallback, aManagerWindow); + + if (aView) + aManagerWindow.loadView(aView); + + ok(aManagerWindow != null, "Should have an add-ons manager window"); + is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI"); + + waitForFocus(function() { + info("window has focus, waiting for manager load"); + wait_for_manager_load(aManagerWindow, function() { + info("Manager waiting for view load"); + wait_for_view_load(aManagerWindow, function() { + resolve(aManagerWindow); + }, null, aLongerTimeout); + }); + }, aManagerWindow); + } + + if (gUseInContentUI) { + info("Loading manager window in tab"); + Services.obs.addObserver(function (aSubject, aTopic, aData) { + Services.obs.removeObserver(arguments.callee, aTopic); + if (aSubject.location.href != MANAGER_URI) { + info("Ignoring load event for " + aSubject.location.href); + return; + } + setup_manager(aSubject); + }, "EM-loaded", false); + + gBrowser.selectedTab = gBrowser.addTab(); + switchToTabHavingURI(MANAGER_URI, true); + } else { + info("Loading manager window in dialog"); + Services.obs.addObserver(function (aSubject, aTopic, aData) { + Services.obs.removeObserver(arguments.callee, aTopic); + setup_manager(aSubject); + }, "EM-loaded", false); + + openDialog(MANAGER_URI); + } + }); + + // The promise resolves with the manager window, so it is passed to the callback + return log_callback(p, aCallback); +} + +function close_manager(aManagerWindow, aCallback, aLongerTimeout) { + let p = new Promise((resolve, reject) => { + requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); + + ok(aManagerWindow != null, "Should have an add-ons manager window to close"); + is(aManagerWindow.location, MANAGER_URI, "Should be closing window with correct URI"); + + aManagerWindow.addEventListener("unload", function() { + try { + dump("Manager window unload handler\n"); + this.removeEventListener("unload", arguments.callee, false); + resolve(); + } catch (e) { + reject(e); + } + }, false); + }); + + info("Telling manager window to close"); + aManagerWindow.close(); + info("Manager window close() call returned"); + + return log_callback(p, aCallback); +} + +function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) { + if (!aManagerWindow) { + return open_manager(aView, aCallback, aLoadCallback); + } + + return close_manager(aManagerWindow) + .then(() => open_manager(aView, aCallback, aLoadCallback)); +} + +function wait_for_window_open(aCallback) { + Services.wm.addListener({ + onOpenWindow: function(aWindow) { + Services.wm.removeListener(this); + + let domwindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + domwindow.addEventListener("load", function() { + domwindow.removeEventListener("load", arguments.callee, false); + executeSoon(function() { + aCallback(domwindow); + }); + }, false); + }, + + onCloseWindow: function(aWindow) { + }, + + onWindowTitleChange: function(aWindow, aTitle) { + } + }); +} + +function get_string(aName, ...aArgs) { + var bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/extensions.properties"); + if (aArgs.length == 0) + return bundle.GetStringFromName(aName); + return bundle.formatStringFromName(aName, aArgs, aArgs.length); +} + +function formatDate(aDate) { + const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIXULChromeRegistry) + .getSelectedLocale("global", true); + const dtOptions = { year: 'numeric', month: 'long', day: 'numeric' }; + return aDate.toLocaleDateString(locale, dtOptions); +} + +function is_hidden(aElement) { + var style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, ""); + if (style.display == "none") + return true; + if (style.visibility != "visible") + return true; + + // Hiding a parent element will hide all its children + if (aElement.parentNode != aElement.ownerDocument) + return is_hidden(aElement.parentNode); + + return false; +} + +function is_element_visible(aElement, aMsg) { + isnot(aElement, null, "Element should not be null, when checking visibility"); + ok(!is_hidden(aElement), aMsg || (aElement + " should be visible")); +} + +function is_element_hidden(aElement, aMsg) { + isnot(aElement, null, "Element should not be null, when checking visibility"); + ok(is_hidden(aElement), aMsg || (aElement + " should be hidden")); +} + +function promiseAddonByID(aId) { + return new Promise(resolve => { + AddonManager.getAddonByID(aId, resolve); + }); +} + +function promiseAddonsByIDs(aIDs) { + return new Promise(resolve => { + AddonManager.getAddonsByIDs(aIDs, resolve); + }); +} +/** + * Install an add-on and call a callback when complete. + * + * The callback will receive the Addon for the installed add-on. + */ +function install_addon(path, cb, pathPrefix=TESTROOT) { + let p = new Promise((resolve, reject) => { + AddonManager.getInstallForURL(pathPrefix + path, (install) => { + install.addListener({ + onInstallEnded: () => resolve(install.addon), + }); + + install.install(); + }, "application/x-xpinstall"); + }); + + return log_callback(p, cb); +} + +function CategoryUtilities(aManagerWindow) { + this.window = aManagerWindow; + + var self = this; + this.window.addEventListener("unload", function() { + self.window.removeEventListener("unload", arguments.callee, false); + self.window = null; + }, false); +} + +CategoryUtilities.prototype = { + window: null, + + get selectedCategory() { + isnot(this.window, null, "Should not get selected category when manager window is not loaded"); + var selectedItem = this.window.document.getElementById("categories").selectedItem; + isnot(selectedItem, null, "A category should be selected"); + var view = this.window.gViewController.parseViewId(selectedItem.value); + return (view.type == "list") ? view.param : view.type; + }, + + get: function(aCategoryType, aAllowMissing) { + isnot(this.window, null, "Should not get category when manager window is not loaded"); + var categories = this.window.document.getElementById("categories"); + + var viewId = "addons://list/" + aCategoryType; + var items = categories.getElementsByAttribute("value", viewId); + if (items.length) + return items[0]; + + viewId = "addons://" + aCategoryType + "/"; + items = categories.getElementsByAttribute("value", viewId); + if (items.length) + return items[0]; + + if (!aAllowMissing) + ok(false, "Should have found a category with type " + aCategoryType); + return null; + }, + + getViewId: function(aCategoryType) { + isnot(this.window, null, "Should not get view id when manager window is not loaded"); + return this.get(aCategoryType).value; + }, + + isVisible: function(aCategory) { + isnot(this.window, null, "Should not check visible state when manager window is not loaded"); + if (aCategory.hasAttribute("disabled") && + aCategory.getAttribute("disabled") == "true") + return false; + + return !is_hidden(aCategory); + }, + + isTypeVisible: function(aCategoryType) { + return this.isVisible(this.get(aCategoryType)); + }, + + open: function(aCategory, aCallback) { + + isnot(this.window, null, "Should not open category when manager window is not loaded"); + ok(this.isVisible(aCategory), "Category should be visible if attempting to open it"); + + EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window); + let p = new Promise((resolve, reject) => wait_for_view_load(this.window, resolve)); + + return log_callback(p, aCallback); + }, + + openType: function(aCategoryType, aCallback) { + return this.open(this.get(aCategoryType), aCallback); + } +} + +function CertOverrideListener(host, bits) { + this.host = host; + this.bits = bits; +} + +CertOverrideListener.prototype = { + host: null, + bits: null, + + getInterface: function (aIID) { + return this.QueryInterface(aIID); + }, + + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIBadCertListener2) || + aIID.equals(Ci.nsIInterfaceRequestor) || + aIID.equals(Ci.nsISupports)) + return this; + + throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE); + }, + + notifyCertProblem: function (socketInfo, sslStatus, targetHost) { + var cert = sslStatus.QueryInterface(Components.interfaces.nsISSLStatus) + .serverCert; + var cos = Cc["@mozilla.org/security/certoverride;1"]. + getService(Ci.nsICertOverrideService); + cos.rememberValidityOverride(this.host, -1, cert, this.bits, false); + return true; + } +} + +// Add overrides for the bad certificates +function addCertOverride(host, bits) { + var req = new XMLHttpRequest(); + try { + req.open("GET", "https://" + host + "/", false); + req.channel.notificationCallbacks = new CertOverrideListener(host, bits); + req.send(null); + } + catch (e) { + // This request will fail since the SSL server is not trusted yet + } +} + +/** *** Mock Provider *****/ + +function MockProvider(aUseAsyncCallbacks, aTypes) { + this.addons = []; + this.installs = []; + this.callbackTimers = []; + this.timerLocations = new Map(); + this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks; + this.types = (aTypes === undefined) ? [{ + id: "extension", + name: "Extensions", + uiPriority: 4000, + flags: AddonManager.TYPE_UI_VIEW_LIST | + AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL, + }] : aTypes; + + var self = this; + registerCleanupFunction(function() { + if (self.started) + self.unregister(); + }); + + this.register(); +} + +MockProvider.prototype = { + addons: null, + installs: null, + started: null, + apiDelay: 10, + callbackTimers: null, + timerLocations: null, + useAsyncCallbacks: null, + types: null, + + /** *** Utility functions *****/ + + /** + * Register this provider with the AddonManager + */ + register: function MP_register() { + info("Registering mock add-on provider"); + AddonManagerPrivate.registerProvider(this, this.types); + }, + + /** + * Unregister this provider with the AddonManager + */ + unregister: function MP_unregister() { + info("Unregistering mock add-on provider"); + AddonManagerPrivate.unregisterProvider(this); + }, + + /** + * Adds an add-on to the list of add-ons that this provider exposes to the + * AddonManager, dispatching appropriate events in the process. + * + * @param aAddon + * The add-on to add + */ + addAddon: function MP_addAddon(aAddon) { + var oldAddons = this.addons.filter(aOldAddon => aOldAddon.id == aAddon.id); + var oldAddon = oldAddons.length > 0 ? oldAddons[0] : null; + + this.addons = this.addons.filter(aOldAddon => aOldAddon.id != aAddon.id); + + this.addons.push(aAddon); + aAddon._provider = this; + + if (!this.started) + return; + + let requiresRestart = (aAddon.operationsRequiringRestart & + AddonManager.OP_NEEDS_RESTART_INSTALL) != 0; + AddonManagerPrivate.callInstallListeners("onExternalInstall", null, aAddon, + oldAddon, requiresRestart) + }, + + /** + * Removes an add-on from the list of add-ons that this provider exposes to + * the AddonManager, dispatching the onUninstalled event in the process. + * + * @param aAddon + * The add-on to add + */ + removeAddon: function MP_removeAddon(aAddon) { + var pos = this.addons.indexOf(aAddon); + if (pos == -1) { + ok(false, "Tried to remove an add-on that wasn't registered with the mock provider"); + return; + } + + this.addons.splice(pos, 1); + + if (!this.started) + return; + + AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon); + }, + + /** + * Adds an add-on install to the list of installs that this provider exposes + * to the AddonManager, dispatching appropriate events in the process. + * + * @param aInstall + * The add-on install to add + */ + addInstall: function MP_addInstall(aInstall) { + this.installs.push(aInstall); + aInstall._provider = this; + + if (!this.started) + return; + + aInstall.callListeners("onNewInstall"); + }, + + removeInstall: function MP_removeInstall(aInstall) { + var pos = this.installs.indexOf(aInstall); + if (pos == -1) { + ok(false, "Tried to remove an install that wasn't registered with the mock provider"); + return; + } + + this.installs.splice(pos, 1); + }, + + /** + * Creates a set of mock add-on objects and adds them to the list of add-ons + * managed by this provider. + * + * @param aAddonProperties + * An array of objects containing properties describing the add-ons + * @return Array of the new MockAddons + */ + createAddons: function MP_createAddons(aAddonProperties) { + var newAddons = []; + for (let addonProp of aAddonProperties) { + let addon = new MockAddon(addonProp.id); + for (let prop in addonProp) { + if (prop == "id") + continue; + if (prop == "applyBackgroundUpdates") { + addon._applyBackgroundUpdates = addonProp[prop]; + continue; + } + if (prop == "appDisabled") { + addon._appDisabled = addonProp[prop]; + continue; + } + addon[prop] = addonProp[prop]; + } + if (!addon.optionsType && !!addon.optionsURL) + addon.optionsType = AddonManager.OPTIONS_TYPE_DIALOG; + + // Make sure the active state matches the passed in properties + addon.isActive = addon.shouldBeActive; + + this.addAddon(addon); + newAddons.push(addon); + } + + return newAddons; + }, + + /** + * Creates a set of mock add-on install objects and adds them to the list + * of installs managed by this provider. + * + * @param aInstallProperties + * An array of objects containing properties describing the installs + * @return Array of the new MockInstalls + */ + createInstalls: function MP_createInstalls(aInstallProperties) { + var newInstalls = []; + for (let installProp of aInstallProperties) { + let install = new MockInstall(installProp.name || null, + installProp.type || null, + null); + for (let prop in installProp) { + switch (prop) { + case "name": + case "type": + break; + case "sourceURI": + install[prop] = NetUtil.newURI(installProp[prop]); + break; + default: + install[prop] = installProp[prop]; + } + } + this.addInstall(install); + newInstalls.push(install); + } + + return newInstalls; + }, + + /** *** AddonProvider implementation *****/ + + /** + * Called to initialize the provider. + */ + startup: function MP_startup() { + this.started = true; + }, + + /** + * Called when the provider should shutdown. + */ + shutdown: function MP_shutdown() { + if (this.callbackTimers.length) { + info("MockProvider: pending callbacks at shutdown(): calling immediately"); + } + while (this.callbackTimers.length > 0) { + // When we notify the callback timer, it removes itself from our array + let timer = this.callbackTimers[0]; + try { + let setAt = this.timerLocations.get(timer); + info("Notifying timer set at " + (setAt || "unknown location")); + timer.callback.notify(timer); + timer.cancel(); + } catch (e) { + info("Timer notify failed: " + e); + } + } + this.callbackTimers = []; + this.timerLocations = null; + + this.started = false; + }, + + /** + * Called to get an Addon with a particular ID. + * + * @param aId + * The ID of the add-on to retrieve + * @param aCallback + * A callback to pass the Addon to + */ + getAddonByID: function MP_getAddon(aId, aCallback) { + for (let addon of this.addons) { + if (addon.id == aId) { + this._delayCallback(aCallback, addon); + return; + } + } + + aCallback(null); + }, + + /** + * Called to get Addons of a particular type. + * + * @param aTypes + * An array of types to fetch. Can be null to get all types. + * @param callback + * A callback to pass an array of Addons to + */ + getAddonsByTypes: function MP_getAddonsByTypes(aTypes, aCallback) { + var addons = this.addons.filter(function(aAddon) { + if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) + return false; + return true; + }); + this._delayCallback(aCallback, addons); + }, + + /** + * Called to get Addons that have pending operations. + * + * @param aTypes + * An array of types to fetch. Can be null to get all types + * @param aCallback + * A callback to pass an array of Addons to + */ + getAddonsWithOperationsByTypes: function MP_getAddonsWithOperationsByTypes(aTypes, aCallback) { + var addons = this.addons.filter(function(aAddon) { + if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) + return false; + return aAddon.pendingOperations != 0; + }); + this._delayCallback(aCallback, addons); + }, + + /** + * Called to get the current AddonInstalls, optionally restricting by type. + * + * @param aTypes + * An array of types or null to get all types + * @param aCallback + * A callback to pass the array of AddonInstalls to + */ + getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) { + var installs = this.installs.filter(function(aInstall) { + // Appear to have actually removed cancelled installs from the provider + if (aInstall.state == AddonManager.STATE_CANCELLED) + return false; + + if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1) + return false; + + return true; + }); + this._delayCallback(aCallback, installs); + }, + + /** + * Called when a new add-on has been enabled when only one add-on of that type + * can be enabled. + * + * @param aId + * The ID of the newly enabled add-on + * @param aType + * The type of the newly enabled add-on + * @param aPendingRestart + * true if the newly enabled add-on will only become enabled after a + * restart + */ + addonChanged: function MP_addonChanged(aId, aType, aPendingRestart) { + // Not implemented + }, + + /** + * Update the appDisabled property for all add-ons. + */ + updateAddonAppDisabledStates: function MP_updateAddonAppDisabledStates() { + // Not needed + }, + + /** + * Called to get an AddonInstall to download and install an add-on from a URL. + * + * @param aUrl + * The URL to be installed + * @param aHash + * A hash for the install + * @param aName + * A name for the install + * @param aIconURL + * An icon URL for the install + * @param aVersion + * A version for the install + * @param aLoadGroup + * An nsILoadGroup to associate requests with + * @param aCallback + * A callback to pass the AddonInstall to + */ + getInstallForURL: function MP_getInstallForURL(aUrl, aHash, aName, aIconURL, + aVersion, aLoadGroup, aCallback) { + // Not yet implemented + }, + + /** + * Called to get an AddonInstall to install an add-on from a local file. + * + * @param aFile + * The file to be installed + * @param aCallback + * A callback to pass the AddonInstall to + */ + getInstallForFile: function MP_getInstallForFile(aFile, aCallback) { + // Not yet implemented + }, + + /** + * Called to test whether installing add-ons is enabled. + * + * @return true if installing is enabled + */ + isInstallEnabled: function MP_isInstallEnabled() { + return false; + }, + + /** + * Called to test whether this provider supports installing a particular + * mimetype. + * + * @param aMimetype + * The mimetype to check for + * @return true if the mimetype is supported + */ + supportsMimetype: function MP_supportsMimetype(aMimetype) { + return false; + }, + + /** + * Called to test whether installing add-ons from a URI is allowed. + * + * @param aUri + * The URI being installed from + * @return true if installing is allowed + */ + isInstallAllowed: function MP_isInstallAllowed(aUri) { + return false; + }, + + + /** *** Internal functions *****/ + + /** + * Delay calling a callback to fake a time-consuming async operation. + * The delay is specified by the apiDelay property, in milliseconds. + * Parameters to send to the callback should be specified as arguments after + * the aCallback argument. + * + * @param aCallback Callback to eventually call + */ + _delayCallback: function MP_delayCallback(aCallback, ...aArgs) { + if (!this.useAsyncCallbacks) { + aCallback(...aArgs); + return; + } + + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + // Need to keep a reference to the timer, so it doesn't get GC'ed + this.callbackTimers.push(timer); + // Capture a stack trace where the timer was set + // needs the 'new Error' hack until bug 1007656 + this.timerLocations.set(timer, Log.stackTrace(new Error("dummy"))); + timer.initWithCallback(() => { + let idx = this.callbackTimers.indexOf(timer); + if (idx == -1) { + dump("MockProvider._delayCallback lost track of timer set at " + + (this.timerLocations.get(timer) || "unknown location") + "\n"); + } else { + this.callbackTimers.splice(idx, 1); + } + this.timerLocations.delete(timer); + aCallback(...aArgs); + }, this.apiDelay, timer.TYPE_ONE_SHOT); + } +}; + +/** *** Mock Addon object for the Mock Provider *****/ + +function MockAddon(aId, aName, aType, aOperationsRequiringRestart) { + // Only set required attributes. + this.id = aId || ""; + this.name = aName || ""; + this.type = aType || "extension"; + this.version = ""; + this.isCompatible = true; + this.providesUpdatesSecurely = true; + this.blocklistState = 0; + this._appDisabled = false; + this._userDisabled = false; + this._applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; + this.scope = AddonManager.SCOPE_PROFILE; + this.isActive = true; + this.creator = ""; + this.pendingOperations = 0; + this._permissions = AddonManager.PERM_CAN_UNINSTALL | + AddonManager.PERM_CAN_ENABLE | + AddonManager.PERM_CAN_DISABLE | + AddonManager.PERM_CAN_UPGRADE; + this.operationsRequiringRestart = (aOperationsRequiringRestart != undefined) ? + aOperationsRequiringRestart : + (AddonManager.OP_NEEDS_RESTART_INSTALL | + AddonManager.OP_NEEDS_RESTART_UNINSTALL | + AddonManager.OP_NEEDS_RESTART_ENABLE | + AddonManager.OP_NEEDS_RESTART_DISABLE); +} + +MockAddon.prototype = { + get isCorrectlySigned() { + if (this.signedState === AddonManager.SIGNEDSTATE_NOT_REQUIRED) + return true; + return this.signedState > AddonManager.SIGNEDSTATE_MISSING; + }, + + get shouldBeActive() { + return !this.appDisabled && !this._userDisabled && + !(this.pendingOperations & AddonManager.PENDING_UNINSTALL); + }, + + get appDisabled() { + return this._appDisabled; + }, + + set appDisabled(val) { + if (val == this._appDisabled) + return val; + + AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["appDisabled"]); + + var currentActive = this.shouldBeActive; + this._appDisabled = val; + var newActive = this.shouldBeActive; + this._updateActiveState(currentActive, newActive); + + return val; + }, + + get userDisabled() { + return this._userDisabled; + }, + + set userDisabled(val) { + if (val == this._userDisabled) + return val; + + var currentActive = this.shouldBeActive; + this._userDisabled = val; + var newActive = this.shouldBeActive; + this._updateActiveState(currentActive, newActive); + + return val; + }, + + get permissions() { + let permissions = this._permissions; + if (this.appDisabled || !this._userDisabled) + permissions &= ~AddonManager.PERM_CAN_ENABLE; + if (this.appDisabled || this._userDisabled) + permissions &= ~AddonManager.PERM_CAN_DISABLE; + return permissions; + }, + + set permissions(val) { + return this._permissions = val; + }, + + get applyBackgroundUpdates() { + return this._applyBackgroundUpdates; + }, + + set applyBackgroundUpdates(val) { + if (val != AddonManager.AUTOUPDATE_DEFAULT && + val != AddonManager.AUTOUPDATE_DISABLE && + val != AddonManager.AUTOUPDATE_ENABLE) { + ok(false, "addon.applyBackgroundUpdates set to an invalid value: " + val); + } + this._applyBackgroundUpdates = val; + AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); + }, + + isCompatibleWith: function(aAppVersion, aPlatformVersion) { + return true; + }, + + findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) { + // Tests can implement this if they need to + }, + + uninstall: function(aAlwaysAllowUndo = false) { + if ((this.operationsRequiringRestart & AddonManager.OP_NEED_RESTART_UNINSTALL) + && this.pendingOperations & AddonManager.PENDING_UNINSTALL) + throw Components.Exception("Add-on is already pending uninstall"); + + var needsRestart = aAlwaysAllowUndo || !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL); + this.pendingOperations |= AddonManager.PENDING_UNINSTALL; + AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart); + if (!needsRestart) { + this.pendingOperations -= AddonManager.PENDING_UNINSTALL; + this._provider.removeAddon(this); + } else if (!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)) { + this.isActive = false; + } + }, + + cancelUninstall: function() { + if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL)) + throw Components.Exception("Add-on is not pending uninstall"); + + this.pendingOperations -= AddonManager.PENDING_UNINSTALL; + this.isActive = this.shouldBeActive; + AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); + }, + + markAsSeen: function() { + this.seen = true; + }, + + _updateActiveState: function(currentActive, newActive) { + if (currentActive == newActive) + return; + + if (newActive == this.isActive) { + this.pendingOperations -= (newActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE); + AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); + } + else if (newActive) { + let needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE); + this.pendingOperations |= AddonManager.PENDING_ENABLE; + AddonManagerPrivate.callAddonListeners("onEnabling", this, needsRestart); + if (!needsRestart) { + this.isActive = newActive; + this.pendingOperations -= AddonManager.PENDING_ENABLE; + AddonManagerPrivate.callAddonListeners("onEnabled", this); + } + } + else { + let needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE); + this.pendingOperations |= AddonManager.PENDING_DISABLE; + AddonManagerPrivate.callAddonListeners("onDisabling", this, needsRestart); + if (!needsRestart) { + this.isActive = newActive; + this.pendingOperations -= AddonManager.PENDING_DISABLE; + AddonManagerPrivate.callAddonListeners("onDisabled", this); + } + } + } +}; + +/** *** Mock AddonInstall object for the Mock Provider *****/ + +function MockInstall(aName, aType, aAddonToInstall) { + this.name = aName || ""; + // Don't expose type until download completed + this._type = aType || "extension"; + this.type = null; + this.version = "1.0"; + this.iconURL = ""; + this.infoURL = ""; + this.state = AddonManager.STATE_AVAILABLE; + this.error = 0; + this.sourceURI = null; + this.file = null; + this.progress = 0; + this.maxProgress = -1; + this.certificate = null; + this.certName = ""; + this.existingAddon = null; + this.addon = null; + this._addonToInstall = aAddonToInstall; + this.listeners = []; + + // Another type of install listener for tests that want to check the results + // of code run from standard install listeners + this.testListeners = []; +} + +MockInstall.prototype = { + install: function() { + switch (this.state) { + case AddonManager.STATE_AVAILABLE: + this.state = AddonManager.STATE_DOWNLOADING; + if (!this.callListeners("onDownloadStarted")) { + this.state = AddonManager.STATE_CANCELLED; + this.callListeners("onDownloadCancelled"); + return; + } + + this.type = this._type; + + // Adding addon to MockProvider to be implemented when needed + if (this._addonToInstall) + this.addon = this._addonToInstall; + else { + this.addon = new MockAddon("", this.name, this.type); + this.addon.version = this.version; + this.addon.pendingOperations = AddonManager.PENDING_INSTALL; + } + this.addon.install = this; + if (this.existingAddon) { + if (!this.addon.id) + this.addon.id = this.existingAddon.id; + this.existingAddon.pendingUpgrade = this.addon; + this.existingAddon.pendingOperations |= AddonManager.PENDING_UPGRADE; + } + + this.state = AddonManager.STATE_DOWNLOADED; + this.callListeners("onDownloadEnded"); + + case AddonManager.STATE_DOWNLOADED: + this.state = AddonManager.STATE_INSTALLING; + if (!this.callListeners("onInstallStarted")) { + this.state = AddonManager.STATE_CANCELLED; + this.callListeners("onInstallCancelled"); + return; + } + + let needsRestart = (this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_INSTALL); + AddonManagerPrivate.callAddonListeners("onInstalling", this.addon, needsRestart); + if (!needsRestart) { + AddonManagerPrivate.callAddonListeners("onInstalled", this.addon); + } + + this.state = AddonManager.STATE_INSTALLED; + this.callListeners("onInstallEnded"); + break; + case AddonManager.STATE_DOWNLOADING: + case AddonManager.STATE_CHECKING: + case AddonManager.STATE_INSTALLING: + // Installation is already running + return; + default: + ok(false, "Cannot start installing when state = " + this.state); + } + }, + + cancel: function() { + switch (this.state) { + case AddonManager.STATE_AVAILABLE: + this.state = AddonManager.STATE_CANCELLED; + break; + case AddonManager.STATE_INSTALLED: + this.state = AddonManager.STATE_CANCELLED; + this._provider.removeInstall(this); + this.callListeners("onInstallCancelled"); + break; + default: + // Handling cancelling when downloading to be implemented when needed + ok(false, "Cannot cancel when state = " + this.state); + } + }, + + + addListener: function(aListener) { + if (!this.listeners.some(i => i == aListener)) + this.listeners.push(aListener); + }, + + removeListener: function(aListener) { + this.listeners = this.listeners.filter(i => i != aListener); + }, + + addTestListener: function(aListener) { + if (!this.testListeners.some(i => i == aListener)) + this.testListeners.push(aListener); + }, + + removeTestListener: function(aListener) { + this.testListeners = this.testListeners.filter(i => i != aListener); + }, + + callListeners: function(aMethod) { + var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners, + this, this.addon); + + // Call test listeners after standard listeners to remove race condition + // between standard and test listeners + for (let listener of this.testListeners) { + try { + if (aMethod in listener) + if (listener[aMethod].call(listener, this, this.addon) === false) + result = false; + } + catch (e) { + ok(false, "Test listener threw exception: " + e); + } + } + + return result; + } +}; + +function waitForCondition(condition, nextTest, errorMsg) { + let tries = 0; + let interval = setInterval(function() { + if (tries >= 30) { + ok(false, errorMsg); + moveOn(); + } + var conditionPassed; + try { + conditionPassed = condition(); + } catch (e) { + ok(false, e + "\n" + e.stack); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, 100); + let moveOn = function() { clearInterval(interval); nextTest(); }; +} + +function getTestPluginTag() { + let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let tags = ph.getPluginTags(); + + // Find the test plugin + for (let i = 0; i < tags.length; i++) { + if (tags[i].name == "Test Plug-in") + return tags[i]; + } + ok(false, "Unable to find plugin"); + return null; +} diff --git a/toolkit/mozapps/extensions/test/browser/more_options.xul b/toolkit/mozapps/extensions/test/browser/more_options.xul new file mode 100644 index 000000000..28dbb0a2e --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/more_options.xul @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/browser/moz.build b/toolkit/mozapps/extensions/test/browser/moz.build new file mode 100644 index 000000000..af04aaeef --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/moz.build @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +BROWSER_CHROME_MANIFESTS += [ + 'browser-window.ini', + 'browser.ini', +] diff --git a/toolkit/mozapps/extensions/test/browser/options.xul b/toolkit/mozapps/extensions/test/browser/options.xul new file mode 100644 index 000000000..1b6827915 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/options.xul @@ -0,0 +1,12 @@ + + + + + Description Text Node + + This is a test, +

+

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs new file mode 100644 index 000000000..92bccd9ec --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs @@ -0,0 +1,24 @@ +// Simple script redirects to the query part of the uri if the cookie "xpinstall" +// has the value "true", otherwise gives a 500 error. + +function handleRequest(request, response) +{ + let cookie = null; + if (request.hasHeader("Cookie")) { + let cookies = request.getHeader("Cookie").split(";"); + for (let i = 0; i < cookies.length; i++) { + if (cookies[i].substring(0, 10) == "xpinstall=") + cookie = cookies[i].substring(10); + } + } + + if (cookie == "true") { + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", request.queryString); + response.write("See " + request.queryString); + } + else { + response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); + response.write("Invalid request"); + } +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi b/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi new file mode 100644 index 000000000..35d7bd5e5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi @@ -0,0 +1 @@ +This is a corrupt zip file diff --git a/toolkit/mozapps/extensions/test/xpinstall/empty.xpi b/toolkit/mozapps/extensions/test/xpinstall/empty.xpi new file mode 100644 index 000000000..74ed2b817 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/empty.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/enabled.html b/toolkit/mozapps/extensions/test/xpinstall/enabled.html new file mode 100644 index 000000000..370cde8fb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/enabled.html @@ -0,0 +1,24 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs new file mode 100644 index 000000000..324a092a3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs @@ -0,0 +1,15 @@ +// Simple script redirects takes the query part of te request and splits it on +// the | character. Anything before is included as the X-Target-Digest header +// the latter part is used as the url to redirect to + +function handleRequest(request, response) +{ + let pos = request.queryString.indexOf("|"); + let header = request.queryString.substring(0, pos); + let url = request.queryString.substring(pos + 1); + + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("X-Target-Digest", header); + response.setHeader("Location", url); + response.write("See " + url); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/head.js b/toolkit/mozapps/extensions/test/xpinstall/head.js new file mode 100644 index 000000000..197fe3fac --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/head.js @@ -0,0 +1,434 @@ +const RELATIVE_DIR = "toolkit/mozapps/extensions/test/xpinstall/"; + +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; +const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR; +const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; +const PROMPT_URL = "chrome://global/content/commonDialog.xul"; +const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xul"; +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; +const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin"; +const PREF_CUSTOM_CONFIRMATION_UI = "xpinstall.customConfirmationUI"; +const CHROME_NAME = "mochikit"; + +function getChromeRoot(path) { + if (path === undefined) { + return "chrome://" + CHROME_NAME + "/content/browser/" + RELATIVE_DIR + } + return getRootDirectory(path); +} + +function extractChromeRoot(path) { + var chromeRootPath = getChromeRoot(path); + var jar = getJar(chromeRootPath); + if (jar) { + var tmpdir = extractJarToTmp(jar); + return "file://" + tmpdir.path + "/"; + } + return chromeRootPath; +} + +Services.prefs.setBoolPref(PREF_CUSTOM_CONFIRMATION_UI, false); +registerCleanupFunction(() => { + Services.prefs.clearUserPref(PREF_CUSTOM_CONFIRMATION_UI); +}); + +/** + * This is a test harness designed to handle responding to UI during the process + * of installing an XPI. A test can set callbacks to hear about specific parts + * of the sequence. + * Before use setup must be called and finish must be called afterwards. + */ +var Harness = { + // If set then the callback is called when an install is attempted and + // software installation is disabled. + installDisabledCallback: null, + // If set then the callback is called when an install is attempted and + // then canceled. + installCancelledCallback: null, + // If set then the callback will be called when an install's origin is blocked. + installOriginBlockedCallback: null, + // If set then the callback will be called when an install is blocked by the + // whitelist. The callback should return true to continue with the install + // anyway. + installBlockedCallback: null, + // If set will be called in the event of authentication being needed to get + // the xpi. Should return a 2 element array of username and password, or + // null to not authenticate. + authenticationCallback: null, + // If set this will be called to allow checking the contents of the xpinstall + // confirmation dialog. The callback should return true to continue the install. + installConfirmCallback: null, + // If set will be called when downloading of an item has begun. + downloadStartedCallback: null, + // If set will be called during the download of an item. + downloadProgressCallback: null, + // If set will be called when an xpi fails to download. + downloadFailedCallback: null, + // If set will be called when an xpi download is cancelled. + downloadCancelledCallback: null, + // If set will be called when downloading of an item has ended. + downloadEndedCallback: null, + // If set will be called when installation by the extension manager of an xpi + // item starts + installStartedCallback: null, + // If set will be called when an xpi fails to install. + installFailedCallback: null, + // If set will be called when each xpi item to be installed completes + // installation. + installEndedCallback: null, + // If set will be called when all triggered items are installed or the install + // is canceled. + installsCompletedCallback: null, + // If set the harness will wait for this DOM event before calling + // installsCompletedCallback + finalContentEvent: null, + + waitingForEvent: false, + pendingCount: null, + installCount: null, + runningInstalls: null, + + waitingForFinish: false, + + // A unique value to return from the installConfirmCallback to indicate that + // the install UI shouldn't be closed automatically + leaveOpen: {}, + + // Setup and tear down functions + setup: function() { + if (!this.waitingForFinish) { + waitForExplicitFinish(); + this.waitingForFinish = true; + + Services.prefs.setBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, false); + + Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); + Services.obs.addObserver(this, "addon-install-started", false); + Services.obs.addObserver(this, "addon-install-disabled", false); + Services.obs.addObserver(this, "addon-install-origin-blocked", false); + Services.obs.addObserver(this, "addon-install-blocked", false); + Services.obs.addObserver(this, "addon-install-failed", false); + Services.obs.addObserver(this, "addon-install-complete", false); + + AddonManager.addInstallListener(this); + + Services.wm.addListener(this); + + var self = this; + registerCleanupFunction(function() { + Services.prefs.clearUserPref(PREF_LOGGING_ENABLED); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIRESECUREORIGIN); + Services.obs.removeObserver(self, "addon-install-started"); + Services.obs.removeObserver(self, "addon-install-disabled"); + Services.obs.removeObserver(self, "addon-install-origin-blocked"); + Services.obs.removeObserver(self, "addon-install-blocked"); + Services.obs.removeObserver(self, "addon-install-failed"); + Services.obs.removeObserver(self, "addon-install-complete"); + + AddonManager.removeInstallListener(self); + + Services.wm.removeListener(self); + + AddonManager.getAllInstalls(function(aInstalls) { + is(aInstalls.length, 0, "Should be no active installs at the end of the test"); + aInstalls.forEach(function(aInstall) { + info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); + aInstall.cancel(); + }); + }); + }); + } + + this.installCount = 0; + this.pendingCount = 0; + this.runningInstalls = []; + }, + + finish: function() { + finish(); + }, + + endTest: function() { + let callback = this.installsCompletedCallback; + let count = this.installCount; + + is(this.runningInstalls.length, 0, "Should be no running installs left"); + this.runningInstalls.forEach(function(aInstall) { + info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); + }); + + this.installOriginBlockedCallback = null; + this.installBlockedCallback = null; + this.authenticationCallback = null; + this.installConfirmCallback = null; + this.downloadStartedCallback = null; + this.downloadProgressCallback = null; + this.downloadCancelledCallback = null; + this.downloadFailedCallback = null; + this.downloadEndedCallback = null; + this.installStartedCallback = null; + this.installFailedCallback = null; + this.installEndedCallback = null; + this.installsCompletedCallback = null; + this.runningInstalls = null; + + if (callback) + executeSoon(() => callback(count)); + }, + + // Window open handling + windowReady: function(window) { + if (window.document.location.href == XPINSTALL_URL) { + if (this.installBlockedCallback) + ok(false, "Should have been blocked by the whitelist"); + this.pendingCount = window.document.getElementById("itemList").childNodes.length; + + // If there is a confirm callback then its return status determines whether + // to install the items or not. If not the test is over. + let result = true; + if (this.installConfirmCallback) { + result = this.installConfirmCallback(window); + if (result === this.leaveOpen) + return; + } + + if (!result) { + window.document.documentElement.cancelDialog(); + } + else { + // Initially the accept button is disabled on a countdown timer + var button = window.document.documentElement.getButton("accept"); + button.disabled = false; + window.document.documentElement.acceptDialog(); + } + } + else if (window.document.location.href == PROMPT_URL) { + var promptType = window.args.promptType; + switch (promptType) { + case "alert": + case "alertCheck": + case "confirmCheck": + case "confirm": + case "confirmEx": + window.document.documentElement.acceptDialog(); + break; + case "promptUserAndPass": + // This is a login dialog, hopefully an authentication prompt + // for the xpi. + if (this.authenticationCallback) { + var auth = this.authenticationCallback(); + if (auth && auth.length == 2) { + window.document.getElementById("loginTextbox").value = auth[0]; + window.document.getElementById("password1Textbox").value = auth[1]; + window.document.documentElement.acceptDialog(); + } + else { + window.document.documentElement.cancelDialog(); + } + } + else { + window.document.documentElement.cancelDialog(); + } + break; + default: + ok(false, "prompt type " + promptType + " not handled in test."); + break; + } + } + }, + + // Install blocked handling + + installDisabled: function(installInfo) { + ok(!!this.installDisabledCallback, "Installation shouldn't have been disabled"); + if (this.installDisabledCallback) + this.installDisabledCallback(installInfo); + this.expectingCancelled = true; + this.expectingCancelled = false; + this.endTest(); + }, + + installCancelled: function(installInfo) { + if (this.expectingCancelled) + return; + + ok(!!this.installCancelledCallback, "Installation shouldn't have been cancelled"); + if (this.installCancelledCallback) + this.installCancelledCallback(installInfo); + this.endTest(); + }, + + installOriginBlocked: function(installInfo) { + ok(!!this.installOriginBlockedCallback, "Shouldn't have been blocked"); + if (this.installOriginBlockedCallback) + this.installOriginBlockedCallback(installInfo); + this.endTest(); + }, + + installBlocked: function(installInfo) { + ok(!!this.installBlockedCallback, "Shouldn't have been blocked by the whitelist"); + if (this.installBlockedCallback && this.installBlockedCallback(installInfo)) { + this.installBlockedCallback = null; + installInfo.install(); + } + else { + this.expectingCancelled = true; + installInfo.installs.forEach(function(install) { + install.cancel(); + }); + this.expectingCancelled = false; + this.endTest(); + } + }, + + // nsIWindowMediatorListener + + onWindowTitleChange: function(window, title) { + }, + + onOpenWindow: function(window) { + var domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindow); + var self = this; + waitForFocus(function() { + self.windowReady(domwindow); + }, domwindow); + }, + + onCloseWindow: function(window) { + }, + + // Addon Install Listener + + onNewInstall: function(install) { + this.runningInstalls.push(install); + + if (this.finalContentEvent && !this.waitingForEvent) { + this.waitingForEvent = true; + info("Waiting for " + this.finalContentEvent); + let mm = gBrowser.selectedBrowser.messageManager; + mm.loadFrameScript(`data:,content.addEventListener("${this.finalContentEvent}", () => { sendAsyncMessage("Test:GotNewInstallEvent"); });`, false); + let win = gBrowser.contentWindow; + let listener = () => { + info("Saw " + this.finalContentEvent); + mm.removeMessageListener("Test:GotNewInstallEvent", listener); + this.waitingForEvent = false; + if (this.pendingCount == 0) + this.endTest(); + } + mm.addMessageListener("Test:GotNewInstallEvent", listener); + } + }, + + onDownloadStarted: function(install) { + this.pendingCount++; + if (this.downloadStartedCallback) + this.downloadStartedCallback(install); + }, + + onDownloadProgress: function(install) { + if (this.downloadProgressCallback) + this.downloadProgressCallback(install); + }, + + onDownloadEnded: function(install) { + if (this.downloadEndedCallback) + this.downloadEndedCallback(install); + }, + + onDownloadCancelled: function(install) { + isnot(this.runningInstalls.indexOf(install), -1, + "Should only see cancelations for started installs"); + this.runningInstalls.splice(this.runningInstalls.indexOf(install), 1); + + if (this.downloadCancelledCallback) + this.downloadCancelledCallback(install); + this.checkTestEnded(); + }, + + onDownloadFailed: function(install) { + if (this.downloadFailedCallback) + this.downloadFailedCallback(install); + this.checkTestEnded(); + }, + + onInstallStarted: function(install) { + if (this.installStartedCallback) + this.installStartedCallback(install); + }, + + onInstallEnded: function(install, addon) { + if (this.installEndedCallback) + this.installEndedCallback(install, addon); + this.installCount++; + this.checkTestEnded(); + }, + + onInstallFailed: function(install) { + if (this.installFailedCallback) + this.installFailedCallback(install); + this.checkTestEnded(); + }, + + checkTestEnded: function() { + if (--this.pendingCount == 0 && !this.waitingForEvent) + this.endTest(); + }, + + // nsIObserver + + observe: function(subject, topic, data) { + var installInfo = subject.QueryInterface(Components.interfaces.amIWebInstallInfo); + switch (topic) { + case "addon-install-started": + is(this.runningInstalls.length, installInfo.installs.length, + "Should have seen the expected number of installs started"); + break; + case "addon-install-disabled": + this.installDisabled(installInfo); + break; + case "addon-install-cancelled": + this.installCancelled(installInfo); + break; + case "addon-install-origin-blocked": + this.installOriginBlocked(installInfo); + break; + case "addon-install-blocked": + this.installBlocked(installInfo); + break; + case "addon-install-failed": + installInfo.installs.forEach(function(aInstall) { + isnot(this.runningInstalls.indexOf(aInstall), -1, + "Should only see failures for started installs"); + + ok(aInstall.error != 0 || aInstall.addon.appDisabled, + "Failed installs should have an error or be appDisabled"); + + this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); + }, this); + break; + case "addon-install-complete": + installInfo.installs.forEach(function(aInstall) { + isnot(this.runningInstalls.indexOf(aInstall), -1, + "Should only see completed events for started installs"); + + is(aInstall.error, 0, "Completed installs should have no error"); + ok(!aInstall.appDisabled, "Completed installs should not be appDisabled"); + + // Complete installs are either in the INSTALLED or CANCELLED state + // since the test may cancel installs the moment they complete. + ok(aInstall.state == AddonManager.STATE_INSTALLED || + aInstall.state == AddonManager.STATE_CANCELLED, + "Completed installs should be in the right state"); + + this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); + }, this); + break; + } + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsIWindowMediatorListener, + Ci.nsISupports]) +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi b/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi new file mode 100644 index 000000000..262ed38a7 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/installchrome.html b/toolkit/mozapps/extensions/test/xpinstall/installchrome.html new file mode 100644 index 000000000..6abee2ef3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/installchrome.html @@ -0,0 +1,22 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html b/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html new file mode 100644 index 000000000..65cab1ef1 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html @@ -0,0 +1,44 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+

+

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html b/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html new file mode 100644 index 000000000..2b302642e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html @@ -0,0 +1,29 @@ + + + + + + + +InstallTrigger frame tests + + + + + + +

InstallTrigger tests

+

+

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi b/toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi new file mode 100644 index 000000000..d52f28c28 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/navigate.html b/toolkit/mozapps/extensions/test/xpinstall/navigate.html new file mode 100644 index 000000000..5a6903eb9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/navigate.html @@ -0,0 +1,26 @@ + + + + + + + +Navigation tests + + + + +

Test Link

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs new file mode 100644 index 000000000..d248bfbc7 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs @@ -0,0 +1,45 @@ +// Script has two modes based on the query string. If the mode is "setup" then +// parameters from the query string configure the redirection. If the mode is +// "redirect" then a redirect is returned + +function handleRequest(request, response) +{ + let parts = request.queryString.split("&"); + let settings = {}; + + parts.forEach(function(aString) { + let [k, v] = aString.split("="); + settings[k] = decodeURIComponent(v); + }) + + if (settings.mode == "setup") { + delete settings.mode; + + // Object states must be an nsISupports + var state = { + settings: settings, + QueryInterface: function(aIid) { + if (aIid.equals(Components.interfaces.nsISupports)) + return settings; + throw Components.results.NS_ERROR_NO_INTERFACE; + } + } + state.wrappedJSObject = state; + + setObjectState("xpinstall-redirect-settings", state); + response.setStatusLine(request.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "text/plain"); + response.write("Setup complete"); + } + else if (settings.mode == "redirect") { + getObjectState("xpinstall-redirect-settings", function(aObject) { + settings = aObject.wrappedJSObject.settings; + }); + + response.setStatusLine(request.httpVersion, 302, "Found"); + for (var name in settings) { + response.setHeader(name, settings[name]); + } + response.write("Done"); + } +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi b/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi new file mode 100644 index 000000000..8e76bd052 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi b/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi new file mode 100644 index 000000000..9fee8f60b Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi new file mode 100644 index 000000000..11fbe1861 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi new file mode 100644 index 000000000..90d3a3ce6 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi new file mode 100644 index 000000000..19b754038 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi new file mode 100644 index 000000000..8c951881e Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi new file mode 100644 index 000000000..09789d189 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed.xpi new file mode 100644 index 000000000..bd7f78b7c Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/signed.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed2.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed2.xpi new file mode 100644 index 000000000..085efbbf7 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/signed2.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs b/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs new file mode 100644 index 000000000..5f767a8f4 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs @@ -0,0 +1,101 @@ +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +const RELATIVE_PATH = "browser/toolkit/mozapps/extensions/test/xpinstall" +const NOTIFICATION_TOPIC = "slowinstall-complete"; + +/** + * Helper function to create a JS object representing the url parameters from + * the request's queryString. + * + * @param aQueryString + * The request's query string. + * @return A JS object representing the url parameters from the request's + * queryString. + */ +function parseQueryString(aQueryString) { + var paramArray = aQueryString.split("&"); + var regex = /^([^=]+)=(.*)$/; + var params = {}; + for (var i = 0, sz = paramArray.length; i < sz; i++) { + var match = regex.exec(paramArray[i]); + if (!match) + throw "Bad parameter in queryString! '" + paramArray[i] + "'"; + params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]); + } + + return params; +} + +function handleRequest(aRequest, aResponse) { + let id = +getState("ID"); + setState("ID", "" + (id + 1)); + + function LOG(str) { + dump("slowinstall.sjs[" + id + "]: " + str + "\n"); + } + + aResponse.setStatusLine(aRequest.httpVersion, 200, "OK"); + + var params = { }; + if (aRequest.queryString) + params = parseQueryString(aRequest.queryString); + + if (params.file) { + let xpiFile = ""; + + function complete_download() { + LOG("Completing download"); + downloadPaused = false; + + try { + // Doesn't seem to be a sane way to read using OS.File and write to an + // nsIOutputStream so here we are. + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(xpiFile); + let stream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + stream.init(file, -1, -1, stream.DEFER_OPEN + stream.CLOSE_ON_EOF); + + NetUtil.asyncCopy(stream, aResponse.bodyOutputStream, () => { + LOG("Download complete"); + aResponse.finish(); + }); + } + catch (e) { + LOG("Exception " + e); + } + } + + let waitForComplete = new Promise(resolve => { + function complete() { + Services.obs.removeObserver(complete, NOTIFICATION_TOPIC); + resolve(); + } + + Services.obs.addObserver(complete, NOTIFICATION_TOPIC, false); + }); + + aResponse.processAsync(); + + OS.File.getCurrentDirectory().then(dir => { + xpiFile = OS.Path.join(dir, ...RELATIVE_PATH.split("/"), params.file); + LOG("Starting slow download of " + xpiFile); + + OS.File.stat(xpiFile).then(info => { + aResponse.setHeader("Content-Type", "binary/octet-stream"); + aResponse.setHeader("Content-Length", info.size.toString()); + + LOG("Download paused"); + waitForComplete.then(complete_download); + }); + }); + } + else if (params.continue) { + dump("slowinstall.sjs: Received signal to complete all current downloads.\n"); + Services.obs.notifyObservers(null, NOTIFICATION_TOPIC, null); + } +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html b/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html new file mode 100644 index 000000000..50083ca90 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html @@ -0,0 +1,20 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/theme.xpi b/toolkit/mozapps/extensions/test/xpinstall/theme.xpi new file mode 100644 index 000000000..74e650b4a Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/theme.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html b/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html new file mode 100644 index 000000000..42e0e1cd0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html @@ -0,0 +1,36 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+

+

+ + diff --git a/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi b/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi new file mode 100644 index 000000000..51b00475a Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi differ diff --git a/toolkit/mozapps/handling/content/dialog.js b/toolkit/mozapps/handling/content/dialog.js new file mode 100644 index 000000000..98a788201 --- /dev/null +++ b/toolkit/mozapps/handling/content/dialog.js @@ -0,0 +1,278 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This dialog builds its content based on arguments passed into it. + * window.arguments[0]: + * The title of the dialog. + * window.arguments[1]: + * The url of the image that appears to the left of the description text + * window.arguments[2]: + * The text of the description that will appear above the choices the user + * can choose from. + * window.arguments[3]: + * The text of the label directly above the choices the user can choose from. + * window.arguments[4]: + * This is the text to be placed in the label for the checkbox. If no text is + * passed (ie, it's an empty string), the checkbox will be hidden. + * window.arguments[5]: + * The accesskey for the checkbox + * window.arguments[6]: + * This is the text that is displayed below the checkbox when it is checked. + * window.arguments[7]: + * This is the nsIHandlerInfo that gives us all our precious information. + * window.arguments[8]: + * This is the nsIURI that we are being brought up for in the first place. + * window.arguments[9]: + * The nsIInterfaceRequestor of the parent window; may be null + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/SharedPromptUtils.jsm"); + + +var dialog = { + // Member Variables + + _handlerInfo: null, + _URI: null, + _itemChoose: null, + _okButton: null, + _windowCtxt: null, + _buttonDisabled: true, + + // Methods + + /** + * This function initializes the content of the dialog. + */ + initialize: function initialize() + { + this._handlerInfo = window.arguments[7].QueryInterface(Ci.nsIHandlerInfo); + this._URI = window.arguments[8].QueryInterface(Ci.nsIURI); + this._windowCtxt = window.arguments[9]; + if (this._windowCtxt) + this._windowCtxt.QueryInterface(Ci.nsIInterfaceRequestor); + this._itemChoose = document.getElementById("item-choose"); + this._okButton = document.documentElement.getButton("accept"); + + var description = { + image: document.getElementById("description-image"), + text: document.getElementById("description-text") + }; + var options = document.getElementById("item-action-text"); + var checkbox = { + desc: document.getElementById("remember"), + text: document.getElementById("remember-text") + }; + + // Setting values + document.title = window.arguments[0]; + description.image.src = window.arguments[1]; + description.text.textContent = window.arguments[2]; + options.value = window.arguments[3]; + checkbox.desc.label = window.arguments[4]; + checkbox.desc.accessKey = window.arguments[5]; + checkbox.text.textContent = window.arguments[6]; + + // Hide stuff that needs to be hidden + if (!checkbox.desc.label) + checkbox.desc.hidden = true; + + // UI is ready, lets populate our list + this.populateList(); + + this._delayHelper = new EnableDelayHelper({ + disableDialog: () => { + this._buttonDisabled = true; + this.updateOKButton(); + }, + enableDialog: () => { + this._buttonDisabled = false; + this.updateOKButton(); + }, + focusTarget: window + }); + }, + + /** + * Populates the list that a user can choose from. + */ + populateList: function populateList() + { + var items = document.getElementById("items"); + var possibleHandlers = this._handlerInfo.possibleApplicationHandlers; + var preferredHandler = this._handlerInfo.preferredApplicationHandler; + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + for (let i = possibleHandlers.length - 1; i >= 0; --i) { + let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp); + let elm = document.createElement("richlistitem"); + elm.setAttribute("type", "handler"); + elm.setAttribute("name", app.name); + elm.obj = app; + + if (app instanceof Ci.nsILocalHandlerApp) { + // See if we have an nsILocalHandlerApp and set the icon + let uri = ios.newFileURI(app.executable); + elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32"); + } + else if (app instanceof Ci.nsIWebHandlerApp) { + let uri = ios.newURI(app.uriTemplate, null, null); + if (/^https?/.test(uri.scheme)) { + // Unfortunately we can't use the favicon service to get the favicon, + // because the service looks for a record with the exact URL we give + // it, and users won't have such records for URLs they don't visit, + // and users won't visit the handler's URL template, they'll only + // visit URLs derived from that template (i.e. with %s in the template + // replaced by the URL of the content being handled). + elm.setAttribute("image", uri.prePath + "/favicon.ico"); + } + elm.setAttribute("description", uri.prePath); + } + else if (app instanceof Ci.nsIDBusHandlerApp) { + elm.setAttribute("description", app.method); + } + else + throw "unknown handler type"; + + items.insertBefore(elm, this._itemChoose); + if (preferredHandler && app == preferredHandler) + this.selectedItem = elm; + } + + if (this._handlerInfo.hasDefaultHandler) { + let elm = document.createElement("richlistitem"); + elm.setAttribute("type", "handler"); + elm.id = "os-default-handler"; + elm.setAttribute("name", this._handlerInfo.defaultDescription); + + items.insertBefore(elm, items.firstChild); + if (this._handlerInfo.preferredAction == + Ci.nsIHandlerInfo.useSystemDefault) + this.selectedItem = elm; + } + items.ensureSelectedElementIsVisible(); + }, + + /** + * Brings up a filepicker and allows a user to choose an application. + */ + chooseApplication: function chooseApplication() + { + var bundle = document.getElementById("base-strings"); + var title = bundle.getString("choose.application.title"); + + var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + fp.init(window, title, Ci.nsIFilePicker.modeOpen); + fp.appendFilters(Ci.nsIFilePicker.filterApps); + + if (fp.show() == Ci.nsIFilePicker.returnOK && fp.file) { + let uri = Cc["@mozilla.org/network/util;1"]. + getService(Ci.nsIIOService). + newFileURI(fp.file); + + let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. + createInstance(Ci.nsILocalHandlerApp); + handlerApp.executable = fp.file; + + // if this application is already in the list, select it and don't add it again + let parent = document.getElementById("items"); + for (let i = 0; i < parent.childNodes.length; ++i) { + let elm = parent.childNodes[i]; + if (elm.obj instanceof Ci.nsILocalHandlerApp && elm.obj.equals(handlerApp)) { + parent.selectedItem = elm; + parent.ensureSelectedElementIsVisible(); + return; + } + } + + let elm = document.createElement("richlistitem"); + elm.setAttribute("type", "handler"); + elm.setAttribute("name", fp.file.leafName); + elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32"); + elm.obj = handlerApp; + + parent.selectedItem = parent.insertBefore(elm, parent.firstChild); + parent.ensureSelectedElementIsVisible(); + } + }, + + /** + * Function called when the OK button is pressed. + */ + onAccept: function onAccept() + { + var checkbox = document.getElementById("remember"); + if (!checkbox.hidden) { + // We need to make sure that the default is properly set now + if (this.selectedItem.obj) { + // default OS handler doesn't have this property + this._handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp; + this._handlerInfo.preferredApplicationHandler = this.selectedItem.obj; + } + else + this._handlerInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault; + } + this._handlerInfo.alwaysAskBeforeHandling = !checkbox.checked; + + var hs = Cc["@mozilla.org/uriloader/handler-service;1"]. + getService(Ci.nsIHandlerService); + hs.store(this._handlerInfo); + + this._handlerInfo.launchWithURI(this._URI, this._windowCtxt); + + return true; + }, + + /** + * Determines if the OK button should be disabled or not + */ + updateOKButton: function updateOKButton() + { + this._okButton.disabled = this._itemChoose.selected || + this._buttonDisabled; + }, + + /** + * Updates the UI based on the checkbox being checked or not. + */ + onCheck: function onCheck() + { + if (document.getElementById("remember").checked) + document.getElementById("remember-text").setAttribute("visible", "true"); + else + document.getElementById("remember-text").removeAttribute("visible"); + }, + + /** + * Function called when the user double clicks on an item of the list + */ + onDblClick: function onDblClick() + { + if (this.selectedItem == this._itemChoose) + this.chooseApplication(); + else + document.documentElement.acceptDialog(); + }, + + // Getters / Setters + + /** + * Returns/sets the selected element in the richlistbox + */ + get selectedItem() + { + return document.getElementById("items").selectedItem; + }, + set selectedItem(aItem) + { + return document.getElementById("items").selectedItem = aItem; + } + +}; diff --git a/toolkit/mozapps/handling/content/dialog.xul b/toolkit/mozapps/handling/content/dialog.xul new file mode 100644 index 000000000..f73ce6d7f --- /dev/null +++ b/toolkit/mozapps/handling/content/dialog.xul @@ -0,0 +1,52 @@ + + + + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul b/toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul
new file mode 100644
index 000000000..12b5302a4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+  

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul b/toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul new file mode 100644 index 000000000..d910adc08 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul @@ -0,0 +1,55 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul b/toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul new file mode 100644 index 000000000..c3f024c73 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul @@ -0,0 +1,46 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul b/toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul new file mode 100644 index 000000000..f399a0096 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul @@ -0,0 +1,46 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul new file mode 100644 index 000000000..1040c19e3 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul @@ -0,0 +1,52 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul b/toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul new file mode 100644 index 000000000..9221a4b98 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul @@ -0,0 +1,52 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul new file mode 100644 index 000000000..8da5c7e97 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul @@ -0,0 +1,52 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul new file mode 100644 index 000000000..db0f33d2f --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul @@ -0,0 +1,52 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul new file mode 100644 index 000000000..736df13a3 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul @@ -0,0 +1,53 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul b/toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul new file mode 100644 index 000000000..cafab4d27 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul @@ -0,0 +1,52 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul new file mode 100644 index 000000000..c1db983a3 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul @@ -0,0 +1,55 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul new file mode 100644 index 000000000..2c28da768 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul @@ -0,0 +1,55 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul b/toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul new file mode 100644 index 000000000..10c34f63b --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul @@ -0,0 +1,53 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul b/toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul new file mode 100644 index 000000000..2c4b389f4 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul @@ -0,0 +1,52 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul b/toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul new file mode 100644 index 000000000..01adb1c3d --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul @@ -0,0 +1,67 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul b/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul new file mode 100644 index 000000000..2e0c2b41e --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul @@ -0,0 +1,68 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul b/toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul new file mode 100644 index 000000000..fc83505f9 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul @@ -0,0 +1,94 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul b/toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul new file mode 100644 index 000000000..a0b29ddea --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul @@ -0,0 +1,55 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul b/toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul new file mode 100644 index 000000000..6db5b9897 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul @@ -0,0 +1,60 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul b/toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul new file mode 100644 index 000000000..6e72a42c1 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul @@ -0,0 +1,60 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul b/toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul new file mode 100644 index 000000000..5b1b826a5 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul @@ -0,0 +1,66 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul b/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul new file mode 100644 index 000000000..b86861012 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul @@ -0,0 +1,65 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul b/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul new file mode 100644 index 000000000..9f7a602c4 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul @@ -0,0 +1,65 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul b/toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul new file mode 100644 index 000000000..faa60c08b --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul @@ -0,0 +1,46 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul b/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul new file mode 100644 index 000000000..3e6f0fec8 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul @@ -0,0 +1,49 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul b/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul new file mode 100644 index 000000000..c60a9fe49 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul @@ -0,0 +1,50 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul b/toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul new file mode 100644 index 000000000..adca621d9 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul @@ -0,0 +1,61 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul b/toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul new file mode 100644 index 000000000..89dd55ea1 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul @@ -0,0 +1,58 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul b/toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul new file mode 100644 index 000000000..13798e5c9 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul @@ -0,0 +1,50 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul b/toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul new file mode 100644 index 000000000..04e613418 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul @@ -0,0 +1,96 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul b/toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul new file mode 100644 index 000000000..c8e8d837b --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul @@ -0,0 +1,50 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul b/toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul new file mode 100644 index 000000000..d88d2092d --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul @@ -0,0 +1,44 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul b/toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul new file mode 100644 index 000000000..142c02baa --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul @@ -0,0 +1,64 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul b/toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul new file mode 100644 index 000000000..6784b9c90 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul @@ -0,0 +1,63 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul b/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul new file mode 100644 index 000000000..a55263eb2 --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul @@ -0,0 +1,112 @@ + + + + + + + + + + +

+ +

+
+
diff --git a/toolkit/mozapps/update/tests/chrome/update.sjs b/toolkit/mozapps/update/tests/chrome/update.sjs new file mode 100644 index 000000000..78bb1b93f --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/update.sjs @@ -0,0 +1,194 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Server side http server script for application update tests. + */ + +const { classes: Cc, interfaces: Ci } = Components; + +const REL_PATH_DATA = "chrome/toolkit/mozapps/update/tests/data/"; + +function getTestDataFile(aFilename) { + let file = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties).get("CurWorkD", Ci.nsILocalFile); + let pathParts = REL_PATH_DATA.split("/"); + for (let i = 0; i < pathParts.length; ++i) { + file.append(pathParts[i]); + } + if (aFilename) { + file.append(aFilename); + } + return file; +} + +function loadHelperScript() { + let scriptFile = getTestDataFile("sharedUpdateXML.js"); + let io = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService2); + let scriptSpec = io.newFileURI(scriptFile).spec; + let scriptloader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + scriptloader.loadSubScript(scriptSpec, this); +} +loadHelperScript(); + +const URL_HOST = "http://example.com"; +const URL_PATH_UPDATE_XML = "/chrome/toolkit/mozapps/update/tests/chrome/update.sjs"; +const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML; +const SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + FILE_SIMPLE_MAR; + +const SLOW_MAR_DOWNLOAD_INTERVAL = 100; +var gTimer; + +function handleRequest(aRequest, aResponse) { + let params = { }; + if (aRequest.queryString) { + params = parseQueryString(aRequest.queryString); + } + + let statusCode = params.statusCode ? parseInt(params.statusCode) : 200; + let statusReason = params.statusReason ? params.statusReason : "OK"; + aResponse.setStatusLine(aRequest.httpVersion, statusCode, statusReason); + aResponse.setHeader("Cache-Control", "no-cache", false); + + // When a mar download is started by the update service it can finish + // downloading before the ui has loaded. By specifying a serviceURL for the + // update patch that points to this file and has a slowDownloadMar param the + // mar will be downloaded asynchronously which will allow the ui to load + // before the download completes. + if (params.slowDownloadMar) { + aResponse.processAsync(); + aResponse.setHeader("Content-Type", "binary/octet-stream"); + aResponse.setHeader("Content-Length", SIZE_SIMPLE_MAR); + var continueFile = getTestDataFile("continue"); + var contents = readFileBytes(getTestDataFile(FILE_SIMPLE_MAR)); + gTimer = Cc["@mozilla.org/timer;1"]. + createInstance(Ci.nsITimer); + gTimer.initWithCallback(function(aTimer) { + if (continueFile.exists()) { + gTimer.cancel(); + aResponse.write(contents); + aResponse.finish(); + } + }, SLOW_MAR_DOWNLOAD_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK); + return; + } + + if (params.uiURL) { + let remoteType = ""; + if (!params.remoteNoTypeAttr && params.uiURL == "BILLBOARD") { + remoteType = " " + params.uiURL.toLowerCase() + "=\"1\""; + } + aResponse.write("" + params.uiURL + + "

this is a test mar that will not affect your " + + "build."); + return; + } + + if (params.xmlMalformed) { + aResponse.write("xml error"); + return; + } + + if (params.noUpdates) { + aResponse.write(getRemoteUpdatesXMLString("")); + return; + } + + if (params.unsupported) { + aResponse.write(getRemoteUpdatesXMLString(" \n")); + return; + } + + let size; + let patches = ""; + if (!params.partialPatchOnly) { + size = SIZE_SIMPLE_MAR + (params.invalidCompleteSize ? "1" : ""); + patches += getRemotePatchString("complete", SERVICE_URL, "SHA512", + SHA512_HASH_SIMPLE_MAR, size); + } + + if (!params.completePatchOnly) { + size = SIZE_SIMPLE_MAR + (params.invalidPartialSize ? "1" : ""); + patches += getRemotePatchString("partial", SERVICE_URL, "SHA512", + SHA512_HASH_SIMPLE_MAR, size); + } + + let type = params.type ? params.type : "major"; + let name = params.name ? params.name : "App Update Test"; + let appVersion = params.appVersion ? params.appVersion : "999999.9"; + let displayVersion = params.displayVersion ? params.displayVersion + : "version " + appVersion; + let buildID = params.buildID ? params.buildID : "01234567890123"; + // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244 +// let detailsURL = params.showDetails ? URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS" : null; + let detailsURL = URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS"; + let showPrompt = params.showPrompt ? "true" : null; + let showNever = params.showNever ? "true" : null; + let promptWaitTime = params.promptWaitTime ? params.promptWaitTime : null; + + let updates = getRemoteUpdateString(patches, type, "App Update Test", + displayVersion, appVersion, buildID, + detailsURL, showPrompt, showNever, + promptWaitTime); + aResponse.write(getRemoteUpdatesXMLString(updates)); +} + +/** + * Helper function to create a JS object representing the url parameters from + * the request's queryString. + * + * @param aQueryString + * The request's query string. + * @return A JS object representing the url parameters from the request's + * queryString. + */ +function parseQueryString(aQueryString) { + let paramArray = aQueryString.split("&"); + let regex = /^([^=]+)=(.*)$/; + let params = {}; + for (let i = 0, sz = paramArray.length; i < sz; i++) { + let match = regex.exec(paramArray[i]); + if (!match) { + throw "Bad parameter in queryString! '" + paramArray[i] + "'"; + } + params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]); + } + + return params; +} + +/** + * Reads the binary contents of a file and returns it as a string. + * + * @param aFile + * The file to read from. + * @return The contents of the file as a string. + */ +function readFileBytes(aFile) { + let fis = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + fis.init(aFile, -1, -1, false); + let bis = Cc["@mozilla.org/binaryinputstream;1"]. + createInstance(Ci.nsIBinaryInputStream); + bis.setInputStream(fis); + let data = []; + let count = fis.available(); + while (count > 0) { + let bytes = bis.readByteArray(Math.min(65535, count)); + data.push(String.fromCharCode.apply(null, bytes)); + count -= bytes.length; + if (bytes.length == 0) { + throw "Nothing read from input stream!"; + } + } + data.join(''); + fis.close(); + return data.toString(); +} diff --git a/toolkit/mozapps/update/tests/chrome/utils.js b/toolkit/mozapps/update/tests/chrome/utils.js new file mode 100644 index 000000000..31d0d2e5a --- /dev/null +++ b/toolkit/mozapps/update/tests/chrome/utils.js @@ -0,0 +1,1011 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test Definition + * + * Most tests can use an array named TESTS that will perform most if not all of + * the necessary checks. Each element in the array must be an object with the + * following possible properties. Additional properties besides the ones listed + * below can be added as needed. + * + * overrideCallback (optional) + * The function to call for the next test. This is typically called when the + * wizard page changes but can also be called for other events by the previous + * test. If this property isn't defined then the defaultCallback function will + * be called. If this property is defined then all other properties are + * optional. + * + * pageid (required unless overrideCallback is specified) + * The expected pageid for the wizard. This property is required unless the + * overrideCallback property is defined. + * + * extraStartFunction (optional) + * The function to call at the beginning of the defaultCallback function. If + * the function returns true the defaultCallback function will return early + * which allows waiting for a specific condition to be evaluated in the + * function specified in the extraStartFunction property before continuing + * with the test. + * + * extraCheckFunction (optional) + * The function to call to perform extra checks in the defaultCallback + * function. + * + * extraDelayedCheckFunction (optional) + * The function to call to perform extra checks in the delayedDefaultCallback + * function. + * + * buttonStates (optional) + * A javascript object representing the expected hidden and disabled attribute + * values for the buttons of the current wizard page. The values are checked + * in the delayedDefaultCallback function. For information about the structure + * of this object refer to the getExpectedButtonStates and checkButtonStates + * functions. + * + * buttonClick (optional) + * The current wizard page button to click at the end of the + * delayedDefaultCallback function. If the buttonClick property is defined + * then the extraDelayedFinishFunction property can't be specified due to race + * conditions in some of the tests and if both of them are specified the test + * will intentionally throw. + * + * extraDelayedFinishFunction (optional) + * The function to call at the end of the delayedDefaultCallback function. + * If the extraDelayedFinishFunction property is defined then the buttonClick + * property can't be specified due to race conditions in some of the tests and + * if both of them are specified the test will intentionally throw. + * + * ranTest (should not be specified) + * When delayedDefaultCallback is called a property named ranTest is added to + * the current test so it is possible to verify that each test in the TESTS + * array has ran. + * + * prefHasUserValue (optional) + * For comparing the expected value defined by this property with the return + * value of prefHasUserValue using gPrefToCheck for the preference name in the + * checkPrefHasUserValue function. + */ + +'use strict'; + +/* globals TESTS, runTest, finishTest */ + +const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr, + utils: Cu } = Components; + +Cu.import("resource://gre/modules/Services.jsm", this); + +const IS_MACOSX = ("nsILocalFileMac" in Ci); +const IS_WIN = ("@mozilla.org/windows-registry-key;1" in Cc); + +// The tests have to use the pageid instead of the pageIndex due to the +// app update wizard's access method being random. +const PAGEID_DUMMY = "dummy"; // Done +const PAGEID_CHECKING = "checking"; // Done +const PAGEID_NO_UPDATES_FOUND = "noupdatesfound"; // Done +const PAGEID_MANUAL_UPDATE = "manualUpdate"; // Done +const PAGEID_UNSUPPORTED = "unsupported"; // Done +const PAGEID_FOUND_BASIC = "updatesfoundbasic"; // Done +const PAGEID_DOWNLOADING = "downloading"; // Done +const PAGEID_ERRORS = "errors"; // Done +const PAGEID_ERROR_EXTRA = "errorextra"; // Done +const PAGEID_ERROR_PATCHING = "errorpatching"; // Done +const PAGEID_FINISHED = "finished"; // Done +const PAGEID_FINISHED_BKGRD = "finishedBackground"; // Done + +const UPDATE_WINDOW_NAME = "Update:Wizard"; + +const URL_HOST = "http://example.com"; +const URL_PATH_UPDATE_XML = "/chrome/toolkit/mozapps/update/tests/chrome/update.sjs"; +const REL_PATH_DATA = "chrome/toolkit/mozapps/update/tests/data"; + +// These two URLs must not contain parameters since tests add their own +// test specific parameters. +const URL_HTTP_UPDATE_XML = URL_HOST + URL_PATH_UPDATE_XML; +const URL_HTTPS_UPDATE_XML = "https://example.com" + URL_PATH_UPDATE_XML; + +const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul"; + +const PREF_APP_UPDATE_INTERVAL = "app.update.interval"; +const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer"; + +const LOG_FUNCTION = info; + +const BIN_SUFFIX = (IS_WIN ? ".exe" : ""); +const FILE_UPDATER_BIN = "updater" + (IS_MACOSX ? ".app" : BIN_SUFFIX); +const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak"; + +var gURLData = URL_HOST + "/" + REL_PATH_DATA + "/"; + +var gTestTimeout = 240000; // 4 minutes +var gTimeoutTimer; + +// The number of SimpleTest.executeSoon calls to perform when waiting on an +// update window to close before giving up. +const CLOSE_WINDOW_TIMEOUT_MAXCOUNT = 10; +// Counter for the SimpleTest.executeSoon when waiting on an update window to +// close before giving up. +var gCloseWindowTimeoutCounter = 0; + +// The following vars are for restoring previous preference values (if present) +// when the test finishes. +var gAppUpdateEnabled; // app.update.enabled +var gAppUpdateServiceEnabled; // app.update.service.enabled +var gAppUpdateStagingEnabled; // app.update.staging.enabled +var gAppUpdateURLDefault; // app.update.url (default prefbranch) + +var gTestCounter = -1; +var gWin; +var gDocElem; +var gPrefToCheck; +var gUseTestUpdater = false; + +// Set to true to log additional information for debugging. To log additional +// information for an individual test set DEBUG_AUS_TEST to true in the test's +// onload function. +var DEBUG_AUS_TEST = true; + +const DATA_URI_SPEC = "chrome://mochitests/content/chrome/toolkit/mozapps/update/tests/data/"; +/* import-globals-from ../data/shared.js */ +Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this); + +/** + * The current test in TESTS array. + */ +this.__defineGetter__("gTest", function() { + return TESTS[gTestCounter]; +}); + +/** + * The current test's callback. This will either return the callback defined in + * the test's overrideCallback property or defaultCallback if the + * overrideCallback property is undefined. + */ +this.__defineGetter__("gCallback", function() { + return gTest.overrideCallback ? gTest.overrideCallback + : defaultCallback; +}); + +/** + * nsIObserver for receiving window open and close notifications. + */ +const gWindowObserver = { + observe: function WO_observe(aSubject, aTopic, aData) { + let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget); + + if (aTopic == "domwindowclosed") { + if (win.location != URI_UPDATE_PROMPT_DIALOG) { + debugDump("domwindowclosed event for window not being tested - " + + "location: " + win.location + "... returning early"); + return; + } + // Allow tests the ability to provide their own function (it must be + // named finishTest) for finishing the test. + try { + finishTest(); + } + catch (e) { + finishTestDefault(); + } + return; + } + + win.addEventListener("load", function WO_observe_onLoad() { + win.removeEventListener("load", WO_observe_onLoad, false); + // Ignore windows other than the update UI window. + if (win.location != URI_UPDATE_PROMPT_DIALOG) { + debugDump("load event for window not being tested - location: " + + win.location + "... returning early"); + return; + } + + // The first wizard page should always be the dummy page. + let pageid = win.document.documentElement.currentPage.pageid; + if (pageid != PAGEID_DUMMY) { + // This should never happen but if it does this will provide a clue + // for diagnosing the cause. + ok(false, "Unexpected load event - pageid got: " + pageid + + ", expected: " + PAGEID_DUMMY + "... returning early"); + return; + } + + gWin = win; + gDocElem = gWin.document.documentElement; + gDocElem.addEventListener("pageshow", onPageShowDefault, false); + }, false); + } +}; + +/** + * Default test run function that can be used by most tests. This function uses + * protective measures to prevent the test from failing provided by + * |runTestDefaultWaitForWindowClosed| helper functions to prevent failure due + * to a previous test failure. + */ +function runTestDefault() { + debugDump("entering"); + + if (!("@mozilla.org/zipwriter;1" in Cc)) { + ok(false, "nsIZipWriter is required to run these tests"); + return; + } + + SimpleTest.waitForExplicitFinish(); + + runTestDefaultWaitForWindowClosed(); +} + +/** + * If an update window is found SimpleTest.executeSoon can callback before the + * update window is fully closed especially with debug builds. If an update + * window is found this function will call itself using SimpleTest.executeSoon + * up to the amount declared in CLOSE_WINDOW_TIMEOUT_MAXCOUNT until the update + * window has closed before continuing the test. + */ +function runTestDefaultWaitForWindowClosed() { + gCloseWindowTimeoutCounter++; + if (gCloseWindowTimeoutCounter > CLOSE_WINDOW_TIMEOUT_MAXCOUNT) { + try { + finishTest(); + } + catch (e) { + finishTestDefault(); + } + return; + } + + // The update window should not be open at this time. If it is the call to + // |closeUpdateWindow| will close it and cause the test to fail. + if (closeUpdateWindow()) { + SimpleTest.executeSoon(runTestDefaultWaitForWindowClosed); + } else { + Services.ww.registerNotification(gWindowObserver); + + gCloseWindowTimeoutCounter = 0; + + setupFiles(); + setupPrefs(); + gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1"); + removeUpdateDirsAndFiles(); + reloadUpdateManagerData(); + setupTimer(gTestTimeout); + SimpleTest.executeSoon(setupTestUpdater); + } +} + +/** + * Default test finish function that can be used by most tests. This function + * uses protective measures to prevent the next test from failing provided by + * |finishTestDefaultWaitForWindowClosed| helper functions to prevent failure + * due to an update window being left open. + */ +function finishTestDefault() { + debugDump("entering"); + if (gTimeoutTimer) { + gTimeoutTimer.cancel(); + gTimeoutTimer = null; + } + + if (gChannel) { + debugDump("channel = " + gChannel); + gChannel = null; + gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer); + } + + verifyTestsRan(); + + resetPrefs(); + gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", ""); + resetFiles(); + removeUpdateDirsAndFiles(); + reloadUpdateManagerData(); + + Services.ww.unregisterNotification(gWindowObserver); + if (gDocElem) { + gDocElem.removeEventListener("pageshow", onPageShowDefault, false); + } + + finishTestRestoreUpdaterBackup(); +} + +/** + * nsITimerCallback for the timeout timer to cleanly finish a test if the Update + * Window doesn't close for a test. This allows the next test to run properly if + * a previous test fails. + * + * @param aTimer + * The nsITimer that fired. + */ +function finishTestTimeout(aTimer) { + ok(false, "Test timed out. Maximum time allowed is " + (gTestTimeout / 1000) + + " seconds"); + + try { + finishTest(); + } + catch (e) { + finishTestDefault(); + } +} + +/** + * When a test finishes this will repeatedly attempt to restore the real updater + * for tests that use the test updater and then call + * finishTestDefaultWaitForWindowClosed after the restore is successful. + */ +function finishTestRestoreUpdaterBackup() { + if (gUseTestUpdater) { + try { + // Windows debug builds keep the updater file in use for a short period of + // time after the updater process exits. + restoreUpdaterBackup(); + } catch (e) { + logTestInfo("Attempt to restore the backed up updater failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(finishTestRestoreUpdaterBackup); + return; + } + } + + finishTestDefaultWaitForWindowClosed(); +} + +/** + * If an update window is found SimpleTest.executeSoon can callback before the + * update window is fully closed especially with debug builds. If an update + * window is found this function will call itself using SimpleTest.executeSoon + * up to the amount declared in CLOSE_WINDOW_TIMEOUT_MAXCOUNT until the update + * window has closed before finishing the test. + */ +function finishTestDefaultWaitForWindowClosed() { + gCloseWindowTimeoutCounter++; + if (gCloseWindowTimeoutCounter > CLOSE_WINDOW_TIMEOUT_MAXCOUNT) { + SimpleTest.requestCompleteLog(); + SimpleTest.finish(); + return; + } + + // The update window should not be open at this time. If it is the call to + // |closeUpdateWindow| will close it and cause the test to fail. + if (closeUpdateWindow()) { + SimpleTest.executeSoon(finishTestDefaultWaitForWindowClosed); + } else { + SimpleTest.finish(); + } +} + +/** + * Default callback for the wizard's documentElement pageshow listener. This + * will return early for event's where the originalTarget's nodeName is not + * wizardpage. + */ +function onPageShowDefault(aEvent) { + if (!gTimeoutTimer) { + debugDump("gTimeoutTimer is null... returning early"); + return; + } + + // Return early if the event's original target isn't for a wizardpage element. + // This check is necessary due to the remotecontent element firing pageshow. + if (aEvent.originalTarget.nodeName != "wizardpage") { + debugDump("only handles events with an originalTarget nodeName of " + + "|wizardpage|. aEvent.originalTarget.nodeName = " + + aEvent.originalTarget.nodeName + "... returning early"); + return; + } + + gTestCounter++; + gCallback(aEvent); +} + +/** + * Default callback that can be used by most tests. + */ +function defaultCallback(aEvent) { + if (!gTimeoutTimer) { + debugDump("gTimeoutTimer is null... returning early"); + return; + } + + debugDump("entering - TESTS[" + gTestCounter + "], pageid: " + gTest.pageid + + ", aEvent.originalTarget.nodeName: " + + aEvent.originalTarget.nodeName); + + if (gTest && gTest.extraStartFunction) { + debugDump("calling extraStartFunction " + gTest.extraStartFunction.name); + if (gTest.extraStartFunction(aEvent)) { + debugDump("extraStartFunction early return"); + return; + } + } + + is(gDocElem.currentPage.pageid, gTest.pageid, + "Checking currentPage.pageid equals " + gTest.pageid + " in pageshow"); + + // Perform extra checks if specified by the test + if (gTest.extraCheckFunction) { + debugDump("calling extraCheckFunction " + gTest.extraCheckFunction.name); + gTest.extraCheckFunction(); + } + + // The wizard page buttons' disabled and hidden attributes are set after the + // pageshow event so use executeSoon to allow them to be set so their disabled + // and hidden attribute values can be checked. + SimpleTest.executeSoon(delayedDefaultCallback); +} + +/** + * Delayed default callback called using executeSoon in defaultCallback which + * allows the wizard page buttons' disabled and hidden attributes to be set + * before checking their values. + */ +function delayedDefaultCallback() { + if (!gTimeoutTimer) { + debugDump("gTimeoutTimer is null... returning early"); + return; + } + + if (!gTest) { + debugDump("gTest is null... returning early"); + return; + } + + debugDump("entering - TESTS[" + gTestCounter + "], pageid: " + gTest.pageid); + + // Verify the pageid hasn't changed after executeSoon was called. + is(gDocElem.currentPage.pageid, gTest.pageid, + "Checking currentPage.pageid equals " + gTest.pageid + " after " + + "executeSoon"); + + checkButtonStates(); + + // Perform delayed extra checks if specified by the test + if (gTest.extraDelayedCheckFunction) { + debugDump("calling extraDelayedCheckFunction " + + gTest.extraDelayedCheckFunction.name); + gTest.extraDelayedCheckFunction(); + } + + // Used to verify that this test has been performed + gTest.ranTest = true; + + if (gTest.buttonClick) { + debugDump("clicking " + gTest.buttonClick + " button"); + if (gTest.extraDelayedFinishFunction) { + throw ("Tests cannot have a buttonClick and an extraDelayedFinishFunction property"); + } + gDocElem.getButton(gTest.buttonClick).click(); + } else if (gTest.extraDelayedFinishFunction) { + debugDump("calling extraDelayedFinishFunction " + + gTest.extraDelayedFinishFunction.name); + gTest.extraDelayedFinishFunction(); + } +} + +/** + * Gets the continue file used to signal the mock http server to continue + * downloading for slow download mar file tests without creating it. + * + * @return nsILocalFile for the continue file. + */ +function getContinueFile() { + let continueFile = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties). + get("CurWorkD", Ci.nsILocalFile); + let continuePath = REL_PATH_DATA + "/continue"; + let continuePathParts = continuePath.split("/"); + for (let i = 0; i < continuePathParts.length; ++i) { + continueFile.append(continuePathParts[i]); + } + return continueFile; +} + +/** + * Creates the continue file used to signal the mock http server to continue + * downloading for slow download mar file tests. + */ +function createContinueFile() { + debugDump("creating 'continue' file for slow mar downloads"); + writeFile(getContinueFile(), ""); +} + +/** + * Removes the continue file used to signal the mock http server to continue + * downloading for slow download mar file tests. + */ +function removeContinueFile() { + let continueFile = getContinueFile(); + if (continueFile.exists()) { + debugDump("removing 'continue' file for slow mar downloads"); + continueFile.remove(false); + } +} + +/** + * Checks the wizard page buttons' disabled and hidden attributes values are + * correct. If an expected button id is not specified then the expected disabled + * and hidden attribute value is true. + */ +function checkButtonStates() { + debugDump("entering - TESTS[" + gTestCounter + "], pageid: " + gTest.pageid); + + const buttonNames = ["extra1", "extra2", "back", "next", "finish", "cancel"]; + let buttonStates = getExpectedButtonStates(); + buttonNames.forEach(function(aButtonName) { + let button = gDocElem.getButton(aButtonName); + let hasHidden = aButtonName in buttonStates && + "hidden" in buttonStates[aButtonName]; + let hidden = hasHidden ? buttonStates[aButtonName].hidden : true; + let hasDisabled = aButtonName in buttonStates && + "disabled" in buttonStates[aButtonName]; + let disabled = hasDisabled ? buttonStates[aButtonName].disabled : true; + is(button.hidden, hidden, "Checking " + aButtonName + " button " + + "hidden attribute value equals " + (hidden ? "true" : "false")); + is(button.disabled, disabled, "Checking " + aButtonName + " button " + + "disabled attribute value equals " + (disabled ? "true" : "false")); + }); +} + +/** + * Returns the expected disabled and hidden attribute values for the buttons of + * the current wizard page. + */ +function getExpectedButtonStates() { + // Allow individual tests to override the expected button states. + if (gTest.buttonStates) { + return gTest.buttonStates; + } + + switch (gTest.pageid) { + case PAGEID_CHECKING: + return {cancel: {disabled: false, hidden: false}}; + case PAGEID_FOUND_BASIC: + if (gTest.neverButton) { + return {extra1: {disabled: false, hidden: false}, + extra2: {disabled: false, hidden: false}, + next: {disabled: false, hidden: false}}; + } + return {extra1: {disabled: false, hidden: false}, + next: {disabled: false, hidden: false}}; + case PAGEID_DOWNLOADING: + return {extra1: {disabled: false, hidden: false}}; + case PAGEID_NO_UPDATES_FOUND: + case PAGEID_MANUAL_UPDATE: + case PAGEID_UNSUPPORTED: + case PAGEID_ERRORS: + case PAGEID_ERROR_EXTRA: + return {finish: {disabled: false, hidden: false}}; + case PAGEID_ERROR_PATCHING: + return {next: { disabled: false, hidden: false}}; + case PAGEID_FINISHED: + case PAGEID_FINISHED_BKGRD: + return {extra1: { disabled: false, hidden: false}, + finish: { disabled: false, hidden: false}}; + } + return null; +} + +/** + * Compares the return value of prefHasUserValue for the preference specified in + * gPrefToCheck with the value passed in the aPrefHasValue parameter or the + * value specified in the current test's prefHasUserValue property if + * aPrefHasValue is undefined. + * + * @param aPrefHasValue (optional) + * The expected value returned from prefHasUserValue for the preference + * specified in gPrefToCheck. If aPrefHasValue is undefined the value + * of the current test's prefHasUserValue property will be used. + */ +function checkPrefHasUserValue(aPrefHasValue) { + let prefHasUserValue = aPrefHasValue === undefined ? gTest.prefHasUserValue + : aPrefHasValue; + is(Services.prefs.prefHasUserValue(gPrefToCheck), prefHasUserValue, + "Checking prefHasUserValue for preference " + gPrefToCheck + " equals " + + (prefHasUserValue ? "true" : "false")); +} + +/** + * Checks whether the link is hidden for a general background update check error + * or not on the errorextra page and that the app.update.backgroundErrors + * preference does not have a user value. + * + * @param aShouldBeHidden (optional) + * The expected value for the label's hidden attribute for the link. If + * aShouldBeHidden is undefined the value of the current test's + * shouldBeHidden property will be used. + */ +function checkErrorExtraPage(aShouldBeHidden) { + let shouldBeHidden = aShouldBeHidden === undefined ? gTest.shouldBeHidden + : aShouldBeHidden; + is(gWin.document.getElementById("errorExtraLinkLabel").hidden, shouldBeHidden, + "Checking errorExtraLinkLabel hidden attribute equals " + + (shouldBeHidden ? "true" : "false")); + + is(gWin.document.getElementById(gTest.displayedTextElem).hidden, false, + "Checking " + gTest.displayedTextElem + " should not be hidden"); + + ok(!Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS), + "Preference " + PREF_APP_UPDATE_BACKGROUNDERRORS + " should not have a " + + "user value"); +} + +/** + * Gets the update version info for the update url parameters to send to + * update.sjs. + * + * @param aAppVersion (optional) + * The application version for the update snippet. If not specified the + * current application version will be used. + * @return The url parameters for the application and platform version to send + * to update.sjs. + */ +function getVersionParams(aAppVersion) { + let appInfo = Services.appinfo; + return "&appVersion=" + (aAppVersion ? aAppVersion : appInfo.version); +} + +/** + * Verifies that all tests ran. + */ +function verifyTestsRan() { + debugDump("entering"); + + // Return early if there are no tests defined. + if (!TESTS) { + return; + } + + gTestCounter = -1; + for (let i = 0; i < TESTS.length; ++i) { + gTestCounter++; + let test = TESTS[i]; + let msg = "Checking if TESTS[" + i + "] test was performed... " + + "callback function name = " + gCallback.name + ", " + + "pageid = " + test.pageid; + ok(test.ranTest, msg); + } +} + +/** + * Creates a backup of files the tests need to modify so they can be restored to + * the original file when the test has finished and then modifies the files. + */ +function setupFiles() { + // Backup the updater-settings.ini file if it exists by moving it. + let baseAppDir = getGREDir(); + let updateSettingsIni = baseAppDir.clone(); + updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI); + if (updateSettingsIni.exists()) { + updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI_BAK); + } + updateSettingsIni = baseAppDir.clone(); + updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI); + writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS); +} + +/** + * For tests that use the test updater restores the backed up real updater if + * it exists and tries again on failure since Windows debug builds at times + * leave the file in use. After success moveRealUpdater is called to continue + * the setup of the test updater. For tests that don't use the test updater + * runTest will be called. + */ +function setupTestUpdater() { + if (!gUseTestUpdater) { + runTest(); + return; + } + + try { + restoreUpdaterBackup(); + } catch (e) { + logTestInfo("Attempt to restore the backed up updater failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(setupTestUpdater); + return; + } + moveRealUpdater(); +} + +/** + * Backs up the real updater and tries again on failure since Windows debug + * builds at times leave the file in use. After success it will call + * copyTestUpdater to continue the setup of the test updater. + */ +function moveRealUpdater() { + try { + // Move away the real updater + let baseAppDir = getAppBaseDir(); + let updater = baseAppDir.clone(); + updater.append(FILE_UPDATER_BIN); + updater.moveTo(baseAppDir, FILE_UPDATER_BIN_BAK); + } catch (e) { + logTestInfo("Attempt to move the real updater out of the way failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(moveRealUpdater); + return; + } + + copyTestUpdater(); +} + +/** + * Copies the test updater so it can be used by tests and tries again on failure + * since Windows debug builds at times leave the file in use. After success it + * will call runTest to continue the test. + */ +function copyTestUpdater() { + try { + // Copy the test updater + let baseAppDir = getAppBaseDir(); + let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile); + let relPath = REL_PATH_DATA; + let pathParts = relPath.split("/"); + for (let i = 0; i < pathParts.length; ++i) { + testUpdaterDir.append(pathParts[i]); + } + + let testUpdater = testUpdaterDir.clone(); + testUpdater.append(FILE_UPDATER_BIN); + testUpdater.copyToFollowingLinks(baseAppDir, FILE_UPDATER_BIN); + } catch (e) { + logTestInfo("Attempt to copy the test updater failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(copyTestUpdater); + return; + } + + runTest(); +} + +/** + * Restores the updater that was backed up. This is called in setupTestUpdater + * before the backup of the real updater is done in case the previous test + * failed to restore the updater, in finishTestDefaultWaitForWindowClosed when + * the test has finished, and in test_9999_cleanup.xul after all tests have + * finished. + */ +function restoreUpdaterBackup() { + let baseAppDir = getAppBaseDir(); + let updater = baseAppDir.clone(); + let updaterBackup = baseAppDir.clone(); + updater.append(FILE_UPDATER_BIN); + updaterBackup.append(FILE_UPDATER_BIN_BAK); + if (updaterBackup.exists()) { + if (updater.exists()) { + updater.remove(true); + } + updaterBackup.moveTo(baseAppDir, FILE_UPDATER_BIN); + } +} + +/** + * Sets the most common preferences used by tests to values used by the majority + * of the tests and when necessary saves the preference's original values if + * present so they can be set back to the original values when the test has + * finished. + */ +function setupPrefs() { + if (DEBUG_AUS_TEST) { + Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, true); + } + + // Prevent nsIUpdateTimerManager from notifying nsIApplicationUpdateService + // to check for updates by setting the app update last update time to the + // current time minus one minute in seconds and the interval time to 12 hours + // in seconds. + let now = Math.round(Date.now() / 1000) - 60; + Services.prefs.setIntPref(PREF_APP_UPDATE_LASTUPDATETIME, now); + Services.prefs.setIntPref(PREF_APP_UPDATE_INTERVAL, 43200); + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ENABLED)) { + gAppUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED); + } + Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, true); + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SERVICE_ENABLED)) { + gAppUpdateServiceEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED); + } + Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false); + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_STAGING_ENABLED)) { + gAppUpdateStagingEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED); + } + Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false); + + Services.prefs.setIntPref(PREF_APP_UPDATE_IDLETIME, 0); + Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 0); + Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false); + Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL, 0); +} + +/** + * Restores files that were backed up for the tests and general file cleanup. + */ +function resetFiles() { + // Restore the backed up updater-settings.ini if it exists. + let baseAppDir = getGREDir(); + let updateSettingsIni = baseAppDir.clone(); + updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI_BAK); + if (updateSettingsIni.exists()) { + updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI); + } + + // Not being able to remove the "updated" directory will not adversely affect + // subsequent tests so wrap it in a try block and don't test whether its + // removal was successful. + let updatedDir; + if (IS_MACOSX) { + updatedDir = getUpdatesDir(); + updatedDir.append(DIR_PATCH); + } else { + updatedDir = getAppBaseDir(); + } + updatedDir.append(DIR_UPDATED); + if (updatedDir.exists()) { + try { + removeDirRecursive(updatedDir); + } + catch (e) { + logTestInfo("Unable to remove directory. Path: " + updatedDir.path + + ", Exception: " + e); + } + } +} + +/** + * Resets the most common preferences used by tests to their original values. + */ +function resetPrefs() { + if (gAppUpdateURLDefault) { + gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, gAppUpdateURLDefault); + } + + if (gAppUpdateEnabled !== undefined) { + Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, gAppUpdateEnabled); + } else if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ENABLED)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED); + } + + if (gAppUpdateServiceEnabled !== undefined) { + Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, gAppUpdateServiceEnabled); + } else if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SERVICE_ENABLED)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ENABLED); + } + + if (gAppUpdateStagingEnabled !== undefined) { + Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, gAppUpdateStagingEnabled); + } else if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_STAGING_ENABLED)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_STAGING_ENABLED); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_IDLETIME)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_IDLETIME); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_PROMPTWAITTIME)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_PROMPTWAITTIME); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_URL_DETAILS)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_URL_DETAILS); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LOG)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_LOG); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SILENT)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_SILENT); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDMAXERRORS)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDMAXERRORS); + } + + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL); + } + + try { + Services.prefs.deleteBranch(PREFBRANCH_APP_UPDATE_NEVER); + } + catch (e) { + } +} + +function setupTimer(aTestTimeout) { + gTestTimeout = aTestTimeout; + if (gTimeoutTimer) { + gTimeoutTimer.cancel(); + gTimeoutTimer = null; + } + gTimeoutTimer = Cc["@mozilla.org/timer;1"]. + createInstance(Ci.nsITimer); + gTimeoutTimer.initWithCallback(finishTestTimeout, gTestTimeout, + Ci.nsITimer.TYPE_ONE_SHOT); +} + +/** + * Closes the update window if it is open and causes the test to fail if an + * update window is found. + * + * @return true if an update window was found, otherwise false. + */ +function closeUpdateWindow() { + let updateWindow = getUpdateWindow(); + if (!updateWindow) { + return false; + } + + ok(false, "Found an existing Update Window from the current or a previous " + + "test... attempting to close it."); + updateWindow.close(); + return true; +} + +/** + * Gets the update window. + * + * @return The nsIDOMWindow for the Update Window if it is open and null + * if it isn't. + */ +function getUpdateWindow() { + return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME); +} + +/** + * Helper for background check errors. + */ +const errorsPrefObserver = { + observedPref: null, + maxErrorPref: null, + + /** + * Sets up a preference observer and sets the associated maximum errors + * preference used for background notification. + * + * @param aObservePref + * The preference to observe. + * @param aMaxErrorPref + * The maximum errors preference. + * @param aMaxErrorCount + * The value to set the maximum errors preference to. + */ + init: function(aObservePref, aMaxErrorPref, aMaxErrorCount) { + this.observedPref = aObservePref; + this.maxErrorPref = aMaxErrorPref; + + let maxErrors = aMaxErrorCount ? aMaxErrorCount : 2; + Services.prefs.setIntPref(aMaxErrorPref, maxErrors); + Services.prefs.addObserver(aObservePref, this, false); + }, + + /** + * Preference observer for the preference specified in |this.observedPref|. + */ + observe: function XPI_observe(aSubject, aTopic, aData) { + if (aData == this.observedPref) { + let errCount = Services.prefs.getIntPref(this.observedPref); + let errMax = Services.prefs.getIntPref(this.maxErrorPref); + if (errCount >= errMax) { + debugDump("removing pref observer"); + Services.prefs.removeObserver(this.observedPref, this); + } else { + debugDump("notifying AUS"); + SimpleTest.executeSoon(function() { + gAUS.notify(null); + }); + } + } + } +}; diff --git a/toolkit/mozapps/update/tests/data/complete.exe b/toolkit/mozapps/update/tests/data/complete.exe new file mode 100644 index 000000000..da9cdf0cc Binary files /dev/null and b/toolkit/mozapps/update/tests/data/complete.exe differ diff --git a/toolkit/mozapps/update/tests/data/complete.mar b/toolkit/mozapps/update/tests/data/complete.mar new file mode 100644 index 000000000..52306e0a2 Binary files /dev/null and b/toolkit/mozapps/update/tests/data/complete.mar differ diff --git a/toolkit/mozapps/update/tests/data/complete.png b/toolkit/mozapps/update/tests/data/complete.png new file mode 100644 index 000000000..2990a539f Binary files /dev/null and b/toolkit/mozapps/update/tests/data/complete.png differ diff --git a/toolkit/mozapps/update/tests/data/complete_log_success_mac b/toolkit/mozapps/update/tests/data/complete_log_success_mac new file mode 100644 index 000000000..4f992a137 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/complete_log_success_mac @@ -0,0 +1,332 @@ +UPDATE TYPE complete +PREPARE REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0 +PREPARE REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png +PREPARE REMOVEFILE Contents/Resources/removed-files +PREPARE REMOVEFILE Contents/Resources/precomplete +PREPARE REMOVEFILE Contents/Resources/2/20/20text0 +PREPARE REMOVEFILE Contents/Resources/2/20/20png0.png +PREPARE REMOVEFILE Contents/Resources/0/0exe0.exe +PREPARE REMOVEFILE Contents/Resources/0/00/00text0 +PREPARE REMOVEFILE Contents/MacOS/exe0.exe +PREPARE REMOVEDIR Contents/Resources/searchplugins/ +PREPARE REMOVEDIR Contents/Resources/defaults/pref/ +PREPARE REMOVEDIR Contents/Resources/defaults/ +PREPARE REMOVEDIR Contents/Resources/2/20/ +PREPARE REMOVEDIR Contents/Resources/2/ +PREPARE REMOVEDIR Contents/Resources/0/00/ +PREPARE REMOVEDIR Contents/Resources/0/ +PREPARE REMOVEDIR Contents/Resources/ +PREPARE REMOVEDIR Contents/MacOS/ +PREPARE REMOVEDIR Contents/ +PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0 +PREPARE ADD Contents/Resources/searchplugins/searchpluginspng1.png +PREPARE ADD Contents/Resources/searchplugins/searchpluginspng0.png +PREPARE ADD Contents/Resources/removed-files +PREPARE ADD Contents/Resources/precomplete +PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +PREPARE ADD Contents/Resources/1/10/10text0 +PREPARE ADD Contents/Resources/0/0exe0.exe +PREPARE ADD Contents/Resources/0/00/00text1 +PREPARE ADD Contents/Resources/0/00/00text0 +PREPARE ADD Contents/Resources/0/00/00png0.png +PREPARE ADD Contents/MacOS/exe0.exe +PREPARE REMOVEDIR Contents/Resources/9/99/ +PREPARE REMOVEDIR Contents/Resources/9/99/ +PREPARE REMOVEDIR Contents/Resources/9/98/ +PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0 +PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1 +PREPARE REMOVEDIR Contents/Resources/9/97/970/ +PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0 +PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1 +PREPARE REMOVEDIR Contents/Resources/9/97/971/ +PREPARE REMOVEDIR Contents/Resources/9/97/ +PREPARE REMOVEFILE Contents/Resources/9/96/96text0 +PREPARE REMOVEFILE Contents/Resources/9/96/96text1 +PREPARE REMOVEDIR Contents/Resources/9/96/ +PREPARE REMOVEDIR Contents/Resources/9/95/ +PREPARE REMOVEDIR Contents/Resources/9/95/ +PREPARE REMOVEDIR Contents/Resources/9/94/ +PREPARE REMOVEDIR Contents/Resources/9/94/ +PREPARE REMOVEDIR Contents/Resources/9/93/ +PREPARE REMOVEDIR Contents/Resources/9/92/ +PREPARE REMOVEDIR Contents/Resources/9/91/ +PREPARE REMOVEDIR Contents/Resources/9/90/ +PREPARE REMOVEDIR Contents/Resources/9/90/ +PREPARE REMOVEDIR Contents/Resources/8/89/ +PREPARE REMOVEDIR Contents/Resources/8/89/ +PREPARE REMOVEDIR Contents/Resources/8/88/ +PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0 +PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1 +PREPARE REMOVEDIR Contents/Resources/8/87/870/ +PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0 +PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1 +PREPARE REMOVEDIR Contents/Resources/8/87/871/ +PREPARE REMOVEDIR Contents/Resources/8/87/ +PREPARE REMOVEFILE Contents/Resources/8/86/86text0 +PREPARE REMOVEFILE Contents/Resources/8/86/86text1 +PREPARE REMOVEDIR Contents/Resources/8/86/ +PREPARE REMOVEDIR Contents/Resources/8/85/ +PREPARE REMOVEDIR Contents/Resources/8/85/ +PREPARE REMOVEDIR Contents/Resources/8/84/ +PREPARE REMOVEDIR Contents/Resources/8/84/ +PREPARE REMOVEDIR Contents/Resources/8/83/ +PREPARE REMOVEDIR Contents/Resources/8/82/ +PREPARE REMOVEDIR Contents/Resources/8/81/ +PREPARE REMOVEDIR Contents/Resources/8/80/ +PREPARE REMOVEDIR Contents/Resources/8/80/ +PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe +PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0 +PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1 +PREPARE REMOVEDIR Contents/Resources/7/70/ +PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe +PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0 +PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1 +PREPARE REMOVEDIR Contents/Resources/7/71/ +PREPARE REMOVEFILE Contents/Resources/7/7text0 +PREPARE REMOVEFILE Contents/Resources/7/7text1 +PREPARE REMOVEDIR Contents/Resources/7/ +PREPARE REMOVEDIR Contents/Resources/6/ +PREPARE REMOVEFILE Contents/Resources/5/5text1 +PREPARE REMOVEFILE Contents/Resources/5/5text0 +PREPARE REMOVEFILE Contents/Resources/5/5test.exe +PREPARE REMOVEFILE Contents/Resources/5/5text0 +PREPARE REMOVEFILE Contents/Resources/5/5text1 +PREPARE REMOVEDIR Contents/Resources/5/ +PREPARE REMOVEFILE Contents/Resources/4/4text1 +PREPARE REMOVEFILE Contents/Resources/4/4text0 +PREPARE REMOVEDIR Contents/Resources/4/ +PREPARE REMOVEFILE Contents/Resources/3/3text1 +PREPARE REMOVEFILE Contents/Resources/3/3text0 +EXECUTE REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0 +EXECUTE REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png +EXECUTE REMOVEFILE Contents/Resources/removed-files +EXECUTE REMOVEFILE Contents/Resources/precomplete +EXECUTE REMOVEFILE Contents/Resources/2/20/20text0 +EXECUTE REMOVEFILE Contents/Resources/2/20/20png0.png +EXECUTE REMOVEFILE Contents/Resources/0/0exe0.exe +EXECUTE REMOVEFILE Contents/Resources/0/00/00text0 +EXECUTE REMOVEFILE Contents/MacOS/exe0.exe +EXECUTE REMOVEDIR Contents/Resources/searchplugins/ +EXECUTE REMOVEDIR Contents/Resources/defaults/pref/ +EXECUTE REMOVEDIR Contents/Resources/defaults/ +EXECUTE REMOVEDIR Contents/Resources/2/20/ +EXECUTE REMOVEDIR Contents/Resources/2/ +EXECUTE REMOVEDIR Contents/Resources/0/00/ +EXECUTE REMOVEDIR Contents/Resources/0/ +EXECUTE REMOVEDIR Contents/Resources/ +EXECUTE REMOVEDIR Contents/MacOS/ +EXECUTE REMOVEDIR Contents/ +EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0 +EXECUTE ADD Contents/Resources/searchplugins/searchpluginspng1.png +EXECUTE ADD Contents/Resources/searchplugins/searchpluginspng0.png +EXECUTE ADD Contents/Resources/removed-files +EXECUTE ADD Contents/Resources/precomplete +EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +EXECUTE ADD Contents/Resources/1/10/10text0 +EXECUTE ADD Contents/Resources/0/0exe0.exe +EXECUTE ADD Contents/Resources/0/00/00text1 +EXECUTE ADD Contents/Resources/0/00/00text0 +EXECUTE ADD Contents/Resources/0/00/00png0.png +EXECUTE ADD Contents/MacOS/exe0.exe +EXECUTE REMOVEDIR Contents/Resources/9/99/ +EXECUTE REMOVEDIR Contents/Resources/9/99/ +EXECUTE REMOVEDIR Contents/Resources/9/98/ +EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext0 +EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext1 +EXECUTE REMOVEDIR Contents/Resources/9/97/970/ +EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext0 +EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext1 +EXECUTE REMOVEDIR Contents/Resources/9/97/971/ +EXECUTE REMOVEDIR Contents/Resources/9/97/ +EXECUTE REMOVEFILE Contents/Resources/9/96/96text0 +EXECUTE REMOVEFILE Contents/Resources/9/96/96text1 +EXECUTE REMOVEDIR Contents/Resources/9/96/ +EXECUTE REMOVEDIR Contents/Resources/9/95/ +EXECUTE REMOVEDIR Contents/Resources/9/95/ +EXECUTE REMOVEDIR Contents/Resources/9/94/ +EXECUTE REMOVEDIR Contents/Resources/9/94/ +EXECUTE REMOVEDIR Contents/Resources/9/93/ +EXECUTE REMOVEDIR Contents/Resources/9/92/ +EXECUTE REMOVEDIR Contents/Resources/9/91/ +EXECUTE REMOVEDIR Contents/Resources/9/90/ +EXECUTE REMOVEDIR Contents/Resources/9/90/ +EXECUTE REMOVEDIR Contents/Resources/8/89/ +EXECUTE REMOVEDIR Contents/Resources/8/89/ +EXECUTE REMOVEDIR Contents/Resources/8/88/ +EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext0 +EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext1 +EXECUTE REMOVEDIR Contents/Resources/8/87/870/ +EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext0 +EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext1 +EXECUTE REMOVEDIR Contents/Resources/8/87/871/ +EXECUTE REMOVEDIR Contents/Resources/8/87/ +EXECUTE REMOVEFILE Contents/Resources/8/86/86text0 +EXECUTE REMOVEFILE Contents/Resources/8/86/86text1 +EXECUTE REMOVEDIR Contents/Resources/8/86/ +EXECUTE REMOVEDIR Contents/Resources/8/85/ +EXECUTE REMOVEDIR Contents/Resources/8/85/ +EXECUTE REMOVEDIR Contents/Resources/8/84/ +EXECUTE REMOVEDIR Contents/Resources/8/84/ +EXECUTE REMOVEDIR Contents/Resources/8/83/ +EXECUTE REMOVEDIR Contents/Resources/8/82/ +EXECUTE REMOVEDIR Contents/Resources/8/81/ +EXECUTE REMOVEDIR Contents/Resources/8/80/ +EXECUTE REMOVEDIR Contents/Resources/8/80/ +EXECUTE REMOVEFILE Contents/Resources/7/70/7xtest.exe +EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext0 +EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext1 +EXECUTE REMOVEDIR Contents/Resources/7/70/ +EXECUTE REMOVEFILE Contents/Resources/7/71/7xtest.exe +EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext0 +EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext1 +EXECUTE REMOVEDIR Contents/Resources/7/71/ +EXECUTE REMOVEFILE Contents/Resources/7/7text0 +EXECUTE REMOVEFILE Contents/Resources/7/7text1 +EXECUTE REMOVEDIR Contents/Resources/7/ +EXECUTE REMOVEDIR Contents/Resources/6/ +EXECUTE REMOVEFILE Contents/Resources/5/5text1 +EXECUTE REMOVEFILE Contents/Resources/5/5text0 +EXECUTE REMOVEFILE Contents/Resources/5/5test.exe +EXECUTE REMOVEFILE Contents/Resources/5/5text0 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEFILE Contents/Resources/5/5text1 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEDIR Contents/Resources/5/ +EXECUTE REMOVEFILE Contents/Resources/4/4text1 +EXECUTE REMOVEFILE Contents/Resources/4/4text0 +EXECUTE REMOVEDIR Contents/Resources/4/ +EXECUTE REMOVEFILE Contents/Resources/3/3text1 +EXECUTE REMOVEFILE Contents/Resources/3/3text0 +FINISH REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0 +FINISH REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png +FINISH REMOVEFILE Contents/Resources/removed-files +FINISH REMOVEFILE Contents/Resources/precomplete +FINISH REMOVEFILE Contents/Resources/2/20/20text0 +FINISH REMOVEFILE Contents/Resources/2/20/20png0.png +FINISH REMOVEFILE Contents/Resources/0/0exe0.exe +FINISH REMOVEFILE Contents/Resources/0/00/00text0 +FINISH REMOVEFILE Contents/MacOS/exe0.exe +FINISH REMOVEDIR Contents/Resources/searchplugins/ +removing directory: Contents/Resources/searchplugins/, rv: 0 +FINISH REMOVEDIR Contents/Resources/defaults/pref/ +removing directory: Contents/Resources/defaults/pref/, rv: 0 +FINISH REMOVEDIR Contents/Resources/defaults/ +removing directory: Contents/Resources/defaults/, rv: 0 +FINISH REMOVEDIR Contents/Resources/2/20/ +FINISH REMOVEDIR Contents/Resources/2/ +FINISH REMOVEDIR Contents/Resources/0/00/ +removing directory: Contents/Resources/0/00/, rv: 0 +FINISH REMOVEDIR Contents/Resources/0/ +removing directory: Contents/Resources/0/, rv: 0 +FINISH REMOVEDIR Contents/Resources/ +removing directory: Contents/Resources/, rv: 0 +FINISH REMOVEDIR Contents/MacOS/ +removing directory: Contents/MacOS/, rv: 0 +FINISH REMOVEDIR Contents/ +removing directory: Contents/, rv: 0 +FINISH ADD Contents/Resources/searchplugins/searchpluginstext0 +FINISH ADD Contents/Resources/searchplugins/searchpluginspng1.png +FINISH ADD Contents/Resources/searchplugins/searchpluginspng0.png +FINISH ADD Contents/Resources/removed-files +FINISH ADD Contents/Resources/precomplete +FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +FINISH ADD Contents/Resources/1/10/10text0 +FINISH ADD Contents/Resources/0/0exe0.exe +FINISH ADD Contents/Resources/0/00/00text1 +FINISH ADD Contents/Resources/0/00/00text0 +FINISH ADD Contents/Resources/0/00/00png0.png +FINISH ADD Contents/MacOS/exe0.exe +FINISH REMOVEDIR Contents/Resources/9/99/ +FINISH REMOVEDIR Contents/Resources/9/99/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/9/98/ +FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0 +FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1 +FINISH REMOVEDIR Contents/Resources/9/97/970/ +FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0 +FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1 +FINISH REMOVEDIR Contents/Resources/9/97/971/ +FINISH REMOVEDIR Contents/Resources/9/97/ +FINISH REMOVEFILE Contents/Resources/9/96/96text0 +FINISH REMOVEFILE Contents/Resources/9/96/96text1 +FINISH REMOVEDIR Contents/Resources/9/96/ +FINISH REMOVEDIR Contents/Resources/9/95/ +FINISH REMOVEDIR Contents/Resources/9/95/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/9/94/ +FINISH REMOVEDIR Contents/Resources/9/94/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/9/93/ +FINISH REMOVEDIR Contents/Resources/9/92/ +removing directory: Contents/Resources/9/92/, rv: 0 +FINISH REMOVEDIR Contents/Resources/9/91/ +removing directory: Contents/Resources/9/91/, rv: 0 +FINISH REMOVEDIR Contents/Resources/9/90/ +FINISH REMOVEDIR Contents/Resources/9/90/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/89/ +FINISH REMOVEDIR Contents/Resources/8/89/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/88/ +FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0 +FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1 +FINISH REMOVEDIR Contents/Resources/8/87/870/ +FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0 +FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1 +FINISH REMOVEDIR Contents/Resources/8/87/871/ +FINISH REMOVEDIR Contents/Resources/8/87/ +FINISH REMOVEFILE Contents/Resources/8/86/86text0 +FINISH REMOVEFILE Contents/Resources/8/86/86text1 +FINISH REMOVEDIR Contents/Resources/8/86/ +FINISH REMOVEDIR Contents/Resources/8/85/ +FINISH REMOVEDIR Contents/Resources/8/85/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/84/ +FINISH REMOVEDIR Contents/Resources/8/84/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/83/ +FINISH REMOVEDIR Contents/Resources/8/82/ +removing directory: Contents/Resources/8/82/, rv: 0 +FINISH REMOVEDIR Contents/Resources/8/81/ +removing directory: Contents/Resources/8/81/, rv: 0 +FINISH REMOVEDIR Contents/Resources/8/80/ +FINISH REMOVEDIR Contents/Resources/8/80/ +directory no longer exists; skipping +FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe +FINISH REMOVEFILE Contents/Resources/7/70/7xtext0 +FINISH REMOVEFILE Contents/Resources/7/70/7xtext1 +FINISH REMOVEDIR Contents/Resources/7/70/ +FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe +FINISH REMOVEFILE Contents/Resources/7/71/7xtext0 +FINISH REMOVEFILE Contents/Resources/7/71/7xtext1 +FINISH REMOVEDIR Contents/Resources/7/71/ +FINISH REMOVEFILE Contents/Resources/7/7text0 +FINISH REMOVEFILE Contents/Resources/7/7text1 +FINISH REMOVEDIR Contents/Resources/7/ +FINISH REMOVEDIR Contents/Resources/6/ +FINISH REMOVEFILE Contents/Resources/5/5text1 +FINISH REMOVEFILE Contents/Resources/5/5text0 +FINISH REMOVEFILE Contents/Resources/5/5test.exe +FINISH REMOVEDIR Contents/Resources/5/ +FINISH REMOVEFILE Contents/Resources/4/4text1 +FINISH REMOVEFILE Contents/Resources/4/4text0 +FINISH REMOVEDIR Contents/Resources/4/ +FINISH REMOVEFILE Contents/Resources/3/3text1 +FINISH REMOVEFILE Contents/Resources/3/3text0 +succeeded +calling QuitProgressUI diff --git a/toolkit/mozapps/update/tests/data/complete_log_success_win b/toolkit/mozapps/update/tests/data/complete_log_success_win new file mode 100644 index 000000000..c5a03dc9d --- /dev/null +++ b/toolkit/mozapps/update/tests/data/complete_log_success_win @@ -0,0 +1,320 @@ +UPDATE TYPE complete +PREPARE REMOVEFILE searchplugins/searchpluginstext0 +PREPARE REMOVEFILE searchplugins/searchpluginspng0.png +PREPARE REMOVEFILE removed-files +PREPARE REMOVEFILE precomplete +PREPARE REMOVEFILE exe0.exe +PREPARE REMOVEFILE 2/20/20text0 +PREPARE REMOVEFILE 2/20/20png0.png +PREPARE REMOVEFILE 0/0exe0.exe +PREPARE REMOVEFILE 0/00/00text0 +PREPARE REMOVEDIR searchplugins/ +PREPARE REMOVEDIR defaults/pref/ +PREPARE REMOVEDIR defaults/ +PREPARE REMOVEDIR 2/20/ +PREPARE REMOVEDIR 2/ +PREPARE REMOVEDIR 0/00/ +PREPARE REMOVEDIR 0/ +PREPARE ADD searchplugins/searchpluginstext0 +PREPARE ADD searchplugins/searchpluginspng1.png +PREPARE ADD searchplugins/searchpluginspng0.png +PREPARE ADD removed-files +PREPARE ADD precomplete +PREPARE ADD exe0.exe +PREPARE ADD distribution/extensions/extensions1/extensions1text0 +PREPARE ADD distribution/extensions/extensions1/extensions1png1.png +PREPARE ADD distribution/extensions/extensions1/extensions1png0.png +PREPARE ADD distribution/extensions/extensions0/extensions0text0 +PREPARE ADD distribution/extensions/extensions0/extensions0png1.png +PREPARE ADD distribution/extensions/extensions0/extensions0png0.png +PREPARE ADD 1/10/10text0 +PREPARE ADD 0/0exe0.exe +PREPARE ADD 0/00/00text1 +PREPARE ADD 0/00/00text0 +PREPARE ADD 0/00/00png0.png +PREPARE REMOVEDIR 9/99/ +PREPARE REMOVEDIR 9/99/ +PREPARE REMOVEDIR 9/98/ +PREPARE REMOVEFILE 9/97/970/97xtext0 +PREPARE REMOVEFILE 9/97/970/97xtext1 +PREPARE REMOVEDIR 9/97/970/ +PREPARE REMOVEFILE 9/97/971/97xtext0 +PREPARE REMOVEFILE 9/97/971/97xtext1 +PREPARE REMOVEDIR 9/97/971/ +PREPARE REMOVEDIR 9/97/ +PREPARE REMOVEFILE 9/96/96text0 +PREPARE REMOVEFILE 9/96/96text1 +PREPARE REMOVEDIR 9/96/ +PREPARE REMOVEDIR 9/95/ +PREPARE REMOVEDIR 9/95/ +PREPARE REMOVEDIR 9/94/ +PREPARE REMOVEDIR 9/94/ +PREPARE REMOVEDIR 9/93/ +PREPARE REMOVEDIR 9/92/ +PREPARE REMOVEDIR 9/91/ +PREPARE REMOVEDIR 9/90/ +PREPARE REMOVEDIR 9/90/ +PREPARE REMOVEDIR 8/89/ +PREPARE REMOVEDIR 8/89/ +PREPARE REMOVEDIR 8/88/ +PREPARE REMOVEFILE 8/87/870/87xtext0 +PREPARE REMOVEFILE 8/87/870/87xtext1 +PREPARE REMOVEDIR 8/87/870/ +PREPARE REMOVEFILE 8/87/871/87xtext0 +PREPARE REMOVEFILE 8/87/871/87xtext1 +PREPARE REMOVEDIR 8/87/871/ +PREPARE REMOVEDIR 8/87/ +PREPARE REMOVEFILE 8/86/86text0 +PREPARE REMOVEFILE 8/86/86text1 +PREPARE REMOVEDIR 8/86/ +PREPARE REMOVEDIR 8/85/ +PREPARE REMOVEDIR 8/85/ +PREPARE REMOVEDIR 8/84/ +PREPARE REMOVEDIR 8/84/ +PREPARE REMOVEDIR 8/83/ +PREPARE REMOVEDIR 8/82/ +PREPARE REMOVEDIR 8/81/ +PREPARE REMOVEDIR 8/80/ +PREPARE REMOVEDIR 8/80/ +PREPARE REMOVEFILE 7/70/7xtest.exe +PREPARE REMOVEFILE 7/70/7xtext0 +PREPARE REMOVEFILE 7/70/7xtext1 +PREPARE REMOVEDIR 7/70/ +PREPARE REMOVEFILE 7/71/7xtest.exe +PREPARE REMOVEFILE 7/71/7xtext0 +PREPARE REMOVEFILE 7/71/7xtext1 +PREPARE REMOVEDIR 7/71/ +PREPARE REMOVEFILE 7/7text0 +PREPARE REMOVEFILE 7/7text1 +PREPARE REMOVEDIR 7/ +PREPARE REMOVEDIR 6/ +PREPARE REMOVEFILE 5/5text1 +PREPARE REMOVEFILE 5/5text0 +PREPARE REMOVEFILE 5/5test.exe +PREPARE REMOVEFILE 5/5text0 +PREPARE REMOVEFILE 5/5text1 +PREPARE REMOVEDIR 5/ +PREPARE REMOVEFILE 4/4text1 +PREPARE REMOVEFILE 4/4text0 +PREPARE REMOVEDIR 4/ +PREPARE REMOVEFILE 3/3text1 +PREPARE REMOVEFILE 3/3text0 +EXECUTE REMOVEFILE searchplugins/searchpluginstext0 +EXECUTE REMOVEFILE searchplugins/searchpluginspng0.png +EXECUTE REMOVEFILE removed-files +EXECUTE REMOVEFILE precomplete +EXECUTE REMOVEFILE exe0.exe +EXECUTE REMOVEFILE 2/20/20text0 +EXECUTE REMOVEFILE 2/20/20png0.png +EXECUTE REMOVEFILE 0/0exe0.exe +EXECUTE REMOVEFILE 0/00/00text0 +EXECUTE REMOVEDIR searchplugins/ +EXECUTE REMOVEDIR defaults/pref/ +EXECUTE REMOVEDIR defaults/ +EXECUTE REMOVEDIR 2/20/ +EXECUTE REMOVEDIR 2/ +EXECUTE REMOVEDIR 0/00/ +EXECUTE REMOVEDIR 0/ +EXECUTE ADD searchplugins/searchpluginstext0 +EXECUTE ADD searchplugins/searchpluginspng1.png +EXECUTE ADD searchplugins/searchpluginspng0.png +EXECUTE ADD removed-files +EXECUTE ADD precomplete +EXECUTE ADD exe0.exe +EXECUTE ADD distribution/extensions/extensions1/extensions1text0 +EXECUTE ADD distribution/extensions/extensions1/extensions1png1.png +EXECUTE ADD distribution/extensions/extensions1/extensions1png0.png +EXECUTE ADD distribution/extensions/extensions0/extensions0text0 +EXECUTE ADD distribution/extensions/extensions0/extensions0png1.png +EXECUTE ADD distribution/extensions/extensions0/extensions0png0.png +EXECUTE ADD 1/10/10text0 +EXECUTE ADD 0/0exe0.exe +EXECUTE ADD 0/00/00text1 +EXECUTE ADD 0/00/00text0 +EXECUTE ADD 0/00/00png0.png +EXECUTE REMOVEDIR 9/99/ +EXECUTE REMOVEDIR 9/99/ +EXECUTE REMOVEDIR 9/98/ +EXECUTE REMOVEFILE 9/97/970/97xtext0 +EXECUTE REMOVEFILE 9/97/970/97xtext1 +EXECUTE REMOVEDIR 9/97/970/ +EXECUTE REMOVEFILE 9/97/971/97xtext0 +EXECUTE REMOVEFILE 9/97/971/97xtext1 +EXECUTE REMOVEDIR 9/97/971/ +EXECUTE REMOVEDIR 9/97/ +EXECUTE REMOVEFILE 9/96/96text0 +EXECUTE REMOVEFILE 9/96/96text1 +EXECUTE REMOVEDIR 9/96/ +EXECUTE REMOVEDIR 9/95/ +EXECUTE REMOVEDIR 9/95/ +EXECUTE REMOVEDIR 9/94/ +EXECUTE REMOVEDIR 9/94/ +EXECUTE REMOVEDIR 9/93/ +EXECUTE REMOVEDIR 9/92/ +EXECUTE REMOVEDIR 9/91/ +EXECUTE REMOVEDIR 9/90/ +EXECUTE REMOVEDIR 9/90/ +EXECUTE REMOVEDIR 8/89/ +EXECUTE REMOVEDIR 8/89/ +EXECUTE REMOVEDIR 8/88/ +EXECUTE REMOVEFILE 8/87/870/87xtext0 +EXECUTE REMOVEFILE 8/87/870/87xtext1 +EXECUTE REMOVEDIR 8/87/870/ +EXECUTE REMOVEFILE 8/87/871/87xtext0 +EXECUTE REMOVEFILE 8/87/871/87xtext1 +EXECUTE REMOVEDIR 8/87/871/ +EXECUTE REMOVEDIR 8/87/ +EXECUTE REMOVEFILE 8/86/86text0 +EXECUTE REMOVEFILE 8/86/86text1 +EXECUTE REMOVEDIR 8/86/ +EXECUTE REMOVEDIR 8/85/ +EXECUTE REMOVEDIR 8/85/ +EXECUTE REMOVEDIR 8/84/ +EXECUTE REMOVEDIR 8/84/ +EXECUTE REMOVEDIR 8/83/ +EXECUTE REMOVEDIR 8/82/ +EXECUTE REMOVEDIR 8/81/ +EXECUTE REMOVEDIR 8/80/ +EXECUTE REMOVEDIR 8/80/ +EXECUTE REMOVEFILE 7/70/7xtest.exe +EXECUTE REMOVEFILE 7/70/7xtext0 +EXECUTE REMOVEFILE 7/70/7xtext1 +EXECUTE REMOVEDIR 7/70/ +EXECUTE REMOVEFILE 7/71/7xtest.exe +EXECUTE REMOVEFILE 7/71/7xtext0 +EXECUTE REMOVEFILE 7/71/7xtext1 +EXECUTE REMOVEDIR 7/71/ +EXECUTE REMOVEFILE 7/7text0 +EXECUTE REMOVEFILE 7/7text1 +EXECUTE REMOVEDIR 7/ +EXECUTE REMOVEDIR 6/ +EXECUTE REMOVEFILE 5/5text1 +EXECUTE REMOVEFILE 5/5text0 +EXECUTE REMOVEFILE 5/5test.exe +EXECUTE REMOVEFILE 5/5text0 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEFILE 5/5text1 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEDIR 5/ +EXECUTE REMOVEFILE 4/4text1 +EXECUTE REMOVEFILE 4/4text0 +EXECUTE REMOVEDIR 4/ +EXECUTE REMOVEFILE 3/3text1 +EXECUTE REMOVEFILE 3/3text0 +FINISH REMOVEFILE searchplugins/searchpluginstext0 +FINISH REMOVEFILE searchplugins/searchpluginspng0.png +FINISH REMOVEFILE removed-files +FINISH REMOVEFILE precomplete +FINISH REMOVEFILE exe0.exe +FINISH REMOVEFILE 2/20/20text0 +FINISH REMOVEFILE 2/20/20png0.png +FINISH REMOVEFILE 0/0exe0.exe +FINISH REMOVEFILE 0/00/00text0 +FINISH REMOVEDIR searchplugins/ +removing directory: searchplugins/, rv: 0 +FINISH REMOVEDIR defaults/pref/ +removing directory: defaults/pref/, rv: 0 +FINISH REMOVEDIR defaults/ +removing directory: defaults/, rv: 0 +FINISH REMOVEDIR 2/20/ +FINISH REMOVEDIR 2/ +FINISH REMOVEDIR 0/00/ +removing directory: 0/00/, rv: 0 +FINISH REMOVEDIR 0/ +removing directory: 0/, rv: 0 +FINISH ADD searchplugins/searchpluginstext0 +FINISH ADD searchplugins/searchpluginspng1.png +FINISH ADD searchplugins/searchpluginspng0.png +FINISH ADD removed-files +FINISH ADD precomplete +FINISH ADD exe0.exe +FINISH ADD distribution/extensions/extensions1/extensions1text0 +FINISH ADD distribution/extensions/extensions1/extensions1png1.png +FINISH ADD distribution/extensions/extensions1/extensions1png0.png +FINISH ADD distribution/extensions/extensions0/extensions0text0 +FINISH ADD distribution/extensions/extensions0/extensions0png1.png +FINISH ADD distribution/extensions/extensions0/extensions0png0.png +FINISH ADD 1/10/10text0 +FINISH ADD 0/0exe0.exe +FINISH ADD 0/00/00text1 +FINISH ADD 0/00/00text0 +FINISH ADD 0/00/00png0.png +FINISH REMOVEDIR 9/99/ +FINISH REMOVEDIR 9/99/ +directory no longer exists; skipping +FINISH REMOVEDIR 9/98/ +FINISH REMOVEFILE 9/97/970/97xtext0 +FINISH REMOVEFILE 9/97/970/97xtext1 +FINISH REMOVEDIR 9/97/970/ +FINISH REMOVEFILE 9/97/971/97xtext0 +FINISH REMOVEFILE 9/97/971/97xtext1 +FINISH REMOVEDIR 9/97/971/ +FINISH REMOVEDIR 9/97/ +FINISH REMOVEFILE 9/96/96text0 +FINISH REMOVEFILE 9/96/96text1 +FINISH REMOVEDIR 9/96/ +FINISH REMOVEDIR 9/95/ +FINISH REMOVEDIR 9/95/ +directory no longer exists; skipping +FINISH REMOVEDIR 9/94/ +FINISH REMOVEDIR 9/94/ +directory no longer exists; skipping +FINISH REMOVEDIR 9/93/ +FINISH REMOVEDIR 9/92/ +removing directory: 9/92/, rv: 0 +FINISH REMOVEDIR 9/91/ +removing directory: 9/91/, rv: 0 +FINISH REMOVEDIR 9/90/ +FINISH REMOVEDIR 9/90/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/89/ +FINISH REMOVEDIR 8/89/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/88/ +FINISH REMOVEFILE 8/87/870/87xtext0 +FINISH REMOVEFILE 8/87/870/87xtext1 +FINISH REMOVEDIR 8/87/870/ +FINISH REMOVEFILE 8/87/871/87xtext0 +FINISH REMOVEFILE 8/87/871/87xtext1 +FINISH REMOVEDIR 8/87/871/ +FINISH REMOVEDIR 8/87/ +FINISH REMOVEFILE 8/86/86text0 +FINISH REMOVEFILE 8/86/86text1 +FINISH REMOVEDIR 8/86/ +FINISH REMOVEDIR 8/85/ +FINISH REMOVEDIR 8/85/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/84/ +FINISH REMOVEDIR 8/84/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/83/ +FINISH REMOVEDIR 8/82/ +removing directory: 8/82/, rv: 0 +FINISH REMOVEDIR 8/81/ +removing directory: 8/81/, rv: 0 +FINISH REMOVEDIR 8/80/ +FINISH REMOVEDIR 8/80/ +directory no longer exists; skipping +FINISH REMOVEFILE 7/70/7xtest.exe +FINISH REMOVEFILE 7/70/7xtext0 +FINISH REMOVEFILE 7/70/7xtext1 +FINISH REMOVEDIR 7/70/ +FINISH REMOVEFILE 7/71/7xtest.exe +FINISH REMOVEFILE 7/71/7xtext0 +FINISH REMOVEFILE 7/71/7xtext1 +FINISH REMOVEDIR 7/71/ +FINISH REMOVEFILE 7/7text0 +FINISH REMOVEFILE 7/7text1 +FINISH REMOVEDIR 7/ +FINISH REMOVEDIR 6/ +FINISH REMOVEFILE 5/5text1 +FINISH REMOVEFILE 5/5text0 +FINISH REMOVEFILE 5/5test.exe +FINISH REMOVEDIR 5/ +FINISH REMOVEFILE 4/4text1 +FINISH REMOVEFILE 4/4text0 +FINISH REMOVEDIR 4/ +FINISH REMOVEFILE 3/3text1 +FINISH REMOVEFILE 3/3text0 +succeeded +calling QuitProgressUI diff --git a/toolkit/mozapps/update/tests/data/complete_mac.mar b/toolkit/mozapps/update/tests/data/complete_mac.mar new file mode 100644 index 000000000..ca1497f4f Binary files /dev/null and b/toolkit/mozapps/update/tests/data/complete_mac.mar differ diff --git a/toolkit/mozapps/update/tests/data/complete_precomplete b/toolkit/mozapps/update/tests/data/complete_precomplete new file mode 100644 index 000000000..ae7a0013f --- /dev/null +++ b/toolkit/mozapps/update/tests/data/complete_precomplete @@ -0,0 +1,18 @@ +remove "searchplugins/searchpluginstext0" +remove "searchplugins/searchpluginspng1.png" +remove "searchplugins/searchpluginspng0.png" +remove "removed-files" +remove "precomplete" +remove "exe0.exe" +remove "1/10/10text0" +remove "0/0exe0.exe" +remove "0/00/00text1" +remove "0/00/00text0" +remove "0/00/00png0.png" +rmdir "searchplugins/" +rmdir "defaults/pref/" +rmdir "defaults/" +rmdir "1/10/" +rmdir "1/" +rmdir "0/00/" +rmdir "0/" diff --git a/toolkit/mozapps/update/tests/data/complete_precomplete_mac b/toolkit/mozapps/update/tests/data/complete_precomplete_mac new file mode 100644 index 000000000..8d81a36d6 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/complete_precomplete_mac @@ -0,0 +1,21 @@ +remove "Contents/Resources/searchplugins/searchpluginstext0" +remove "Contents/Resources/searchplugins/searchpluginspng1.png" +remove "Contents/Resources/searchplugins/searchpluginspng0.png" +remove "Contents/Resources/removed-files" +remove "Contents/Resources/precomplete" +remove "Contents/Resources/1/10/10text0" +remove "Contents/Resources/0/0exe0.exe" +remove "Contents/Resources/0/00/00text1" +remove "Contents/Resources/0/00/00text0" +remove "Contents/Resources/0/00/00png0.png" +remove "Contents/MacOS/exe0.exe" +rmdir "Contents/Resources/searchplugins/" +rmdir "Contents/Resources/defaults/pref/" +rmdir "Contents/Resources/defaults/" +rmdir "Contents/Resources/1/10/" +rmdir "Contents/Resources/1/" +rmdir "Contents/Resources/0/00/" +rmdir "Contents/Resources/0/" +rmdir "Contents/Resources/" +rmdir "Contents/MacOS/" +rmdir "Contents/" diff --git a/toolkit/mozapps/update/tests/data/complete_removed-files b/toolkit/mozapps/update/tests/data/complete_removed-files new file mode 100644 index 000000000..e45c43c1f --- /dev/null +++ b/toolkit/mozapps/update/tests/data/complete_removed-files @@ -0,0 +1,41 @@ +text0 +text1 +3/3text0 +3/3text1 +4/exe0.exe +4/4text0 +4/4text1 +4/ +5/5text0 +5/5text1 +5/* +6/ +7/* +8/80/ +8/81/ +8/82/ +8/83/ +8/84/ +8/85/* +8/86/* +8/87/* +8/88/* +8/89/* +8/80/ +8/84/* +8/85/* +8/89/ +9/90/ +9/91/ +9/92/ +9/93/ +9/94/ +9/95/* +9/96/* +9/97/* +9/98/* +9/99/* +9/90/ +9/94/* +9/95/* +9/99/ diff --git a/toolkit/mozapps/update/tests/data/complete_removed-files_mac b/toolkit/mozapps/update/tests/data/complete_removed-files_mac new file mode 100644 index 000000000..955dc5b34 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/complete_removed-files_mac @@ -0,0 +1,41 @@ +Contents/Resources/text0 +Contents/Resources/text1 +Contents/Resources/3/3text0 +Contents/Resources/3/3text1 +Contents/Resources/4/exe0.exe +Contents/Resources/4/4text0 +Contents/Resources/4/4text1 +Contents/Resources/4/ +Contents/Resources/5/5text0 +Contents/Resources/5/5text1 +Contents/Resources/5/* +Contents/Resources/6/ +Contents/Resources/7/* +Contents/Resources/8/80/ +Contents/Resources/8/81/ +Contents/Resources/8/82/ +Contents/Resources/8/83/ +Contents/Resources/8/84/ +Contents/Resources/8/85/* +Contents/Resources/8/86/* +Contents/Resources/8/87/* +Contents/Resources/8/88/* +Contents/Resources/8/89/* +Contents/Resources/8/80/ +Contents/Resources/8/84/* +Contents/Resources/8/85/* +Contents/Resources/8/89/ +Contents/Resources/9/90/ +Contents/Resources/9/91/ +Contents/Resources/9/92/ +Contents/Resources/9/93/ +Contents/Resources/9/94/ +Contents/Resources/9/95/* +Contents/Resources/9/96/* +Contents/Resources/9/97/* +Contents/Resources/9/98/* +Contents/Resources/9/99/* +Contents/Resources/9/90/ +Contents/Resources/9/94/* +Contents/Resources/9/95/* +Contents/Resources/9/99/ diff --git a/toolkit/mozapps/update/tests/data/complete_update_manifest b/toolkit/mozapps/update/tests/data/complete_update_manifest new file mode 100644 index 000000000..383a324f6 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/complete_update_manifest @@ -0,0 +1,59 @@ +type "complete" +add "precomplete" +add "searchplugins/searchpluginstext0" +add "searchplugins/searchpluginspng1.png" +add "searchplugins/searchpluginspng0.png" +add "removed-files" +add-if "extensions/extensions1" "extensions/extensions1/extensions1text0" +add-if "extensions/extensions1" "extensions/extensions1/extensions1png1.png" +add-if "extensions/extensions1" "extensions/extensions1/extensions1png0.png" +add-if "extensions/extensions0" "extensions/extensions0/extensions0text0" +add-if "extensions/extensions0" "extensions/extensions0/extensions0png1.png" +add-if "extensions/extensions0" "extensions/extensions0/extensions0png0.png" +add "exe0.exe" +add "1/10/10text0" +add "0/0exe0.exe" +add "0/00/00text1" +add "0/00/00text0" +add "0/00/00png0.png" +remove "text1" +remove "text0" +rmrfdir "9/99/" +rmdir "9/99/" +rmrfdir "9/98/" +rmrfdir "9/97/" +rmrfdir "9/96/" +rmrfdir "9/95/" +rmrfdir "9/95/" +rmrfdir "9/94/" +rmdir "9/94/" +rmdir "9/93/" +rmdir "9/92/" +rmdir "9/91/" +rmdir "9/90/" +rmdir "9/90/" +rmrfdir "8/89/" +rmdir "8/89/" +rmrfdir "8/88/" +rmrfdir "8/87/" +rmrfdir "8/86/" +rmrfdir "8/85/" +rmrfdir "8/85/" +rmrfdir "8/84/" +rmdir "8/84/" +rmdir "8/83/" +rmdir "8/82/" +rmdir "8/81/" +rmdir "8/80/" +rmdir "8/80/" +rmrfdir "7/" +rmdir "6/" +remove "5/5text1" +remove "5/5text0" +rmrfdir "5/" +remove "4/exe0.exe" +remove "4/4text1" +remove "4/4text0" +rmdir "4/" +remove "3/3text1" +remove "3/3text0" diff --git a/toolkit/mozapps/update/tests/data/old_version.mar b/toolkit/mozapps/update/tests/data/old_version.mar new file mode 100644 index 000000000..31550698a Binary files /dev/null and b/toolkit/mozapps/update/tests/data/old_version.mar differ diff --git a/toolkit/mozapps/update/tests/data/partial.exe b/toolkit/mozapps/update/tests/data/partial.exe new file mode 100644 index 000000000..3949fd2a0 Binary files /dev/null and b/toolkit/mozapps/update/tests/data/partial.exe differ diff --git a/toolkit/mozapps/update/tests/data/partial.mar b/toolkit/mozapps/update/tests/data/partial.mar new file mode 100644 index 000000000..789d3d98d Binary files /dev/null and b/toolkit/mozapps/update/tests/data/partial.mar differ diff --git a/toolkit/mozapps/update/tests/data/partial.png b/toolkit/mozapps/update/tests/data/partial.png new file mode 100644 index 000000000..9246f586c Binary files /dev/null and b/toolkit/mozapps/update/tests/data/partial.png differ diff --git a/toolkit/mozapps/update/tests/data/partial_log_failure_mac b/toolkit/mozapps/update/tests/data/partial_log_failure_mac new file mode 100644 index 000000000..3b2933ebd --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_log_failure_mac @@ -0,0 +1,192 @@ +UPDATE TYPE partial +PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0 +PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng1.png +PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng0.png +PREPARE ADD Contents/Resources/precomplete +PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +PREPARE PATCH Contents/Resources/0/0exe0.exe +PREPARE ADD Contents/Resources/0/00/00text0 +PREPARE PATCH Contents/Resources/0/00/00png0.png +PREPARE PATCH Contents/MacOS/exe0.exe +PREPARE ADD Contents/Resources/2/20/20text0 +PREPARE ADD Contents/Resources/2/20/20png0.png +PREPARE ADD Contents/Resources/0/00/00text2 +PREPARE REMOVEFILE Contents/Resources/1/10/10text0 +PREPARE REMOVEFILE Contents/Resources/0/00/00text1 +PREPARE REMOVEDIR Contents/Resources/9/99/ +PREPARE REMOVEDIR Contents/Resources/9/99/ +PREPARE REMOVEDIR Contents/Resources/9/98/ +PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0 +PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1 +PREPARE REMOVEDIR Contents/Resources/9/97/970/ +PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0 +PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1 +PREPARE REMOVEDIR Contents/Resources/9/97/971/ +PREPARE REMOVEDIR Contents/Resources/9/97/ +PREPARE REMOVEFILE Contents/Resources/9/96/96text0 +PREPARE REMOVEFILE Contents/Resources/9/96/96text1 +PREPARE REMOVEDIR Contents/Resources/9/96/ +PREPARE REMOVEDIR Contents/Resources/9/95/ +PREPARE REMOVEDIR Contents/Resources/9/95/ +PREPARE REMOVEDIR Contents/Resources/9/94/ +PREPARE REMOVEDIR Contents/Resources/9/94/ +PREPARE REMOVEDIR Contents/Resources/9/93/ +PREPARE REMOVEDIR Contents/Resources/9/92/ +PREPARE REMOVEDIR Contents/Resources/9/91/ +PREPARE REMOVEDIR Contents/Resources/9/90/ +PREPARE REMOVEDIR Contents/Resources/9/90/ +PREPARE REMOVEDIR Contents/Resources/8/89/ +PREPARE REMOVEDIR Contents/Resources/8/89/ +PREPARE REMOVEDIR Contents/Resources/8/88/ +PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0 +PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1 +PREPARE REMOVEDIR Contents/Resources/8/87/870/ +PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0 +PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1 +PREPARE REMOVEDIR Contents/Resources/8/87/871/ +PREPARE REMOVEDIR Contents/Resources/8/87/ +PREPARE REMOVEFILE Contents/Resources/8/86/86text0 +PREPARE REMOVEFILE Contents/Resources/8/86/86text1 +PREPARE REMOVEDIR Contents/Resources/8/86/ +PREPARE REMOVEDIR Contents/Resources/8/85/ +PREPARE REMOVEDIR Contents/Resources/8/85/ +PREPARE REMOVEDIR Contents/Resources/8/84/ +PREPARE REMOVEDIR Contents/Resources/8/84/ +PREPARE REMOVEDIR Contents/Resources/8/83/ +PREPARE REMOVEDIR Contents/Resources/8/82/ +PREPARE REMOVEDIR Contents/Resources/8/81/ +PREPARE REMOVEDIR Contents/Resources/8/80/ +PREPARE REMOVEDIR Contents/Resources/8/80/ +PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe +PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0 +PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1 +PREPARE REMOVEDIR Contents/Resources/7/70/ +PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe +PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0 +PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1 +PREPARE REMOVEDIR Contents/Resources/7/71/ +PREPARE REMOVEFILE Contents/Resources/7/7text0 +PREPARE REMOVEFILE Contents/Resources/7/7text1 +PREPARE REMOVEDIR Contents/Resources/7/ +PREPARE REMOVEDIR Contents/Resources/6/ +PREPARE REMOVEFILE Contents/Resources/5/5text1 +PREPARE REMOVEFILE Contents/Resources/5/5text0 +PREPARE REMOVEFILE Contents/Resources/5/5test.exe +PREPARE REMOVEFILE Contents/Resources/5/5text0 +PREPARE REMOVEFILE Contents/Resources/5/5text1 +PREPARE REMOVEDIR Contents/Resources/5/ +PREPARE REMOVEFILE Contents/Resources/4/4text1 +PREPARE REMOVEFILE Contents/Resources/4/4text0 +PREPARE REMOVEDIR Contents/Resources/4/ +PREPARE REMOVEFILE Contents/Resources/3/3text1 +PREPARE REMOVEFILE Contents/Resources/3/3text0 +PREPARE REMOVEDIR Contents/Resources/1/10/ +PREPARE REMOVEDIR Contents/Resources/1/ +EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0 +EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng1.png +EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng0.png +EXECUTE ADD Contents/Resources/precomplete +EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +EXECUTE PATCH Contents/Resources/0/0exe0.exe +LoadSourceFile: destination file size 776 does not match expected size 79872 +LoadSourceFile failed +### execution failed +FINISH ADD Contents/Resources/searchplugins/searchpluginstext0 +FINISH PATCH Contents/Resources/searchplugins/searchpluginspng1.png +FINISH PATCH Contents/Resources/searchplugins/searchpluginspng0.png +FINISH ADD Contents/Resources/precomplete +FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +backup_restore: backup file doesn't exist: Contents/Resources/distribution/extensions/extensions1/extensions1text0.moz-backup +FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +FINISH PATCH Contents/Resources/0/0exe0.exe +backup_restore: backup file doesn't exist: Contents/Resources/0/0exe0.exe.moz-backup +FINISH ADD Contents/Resources/0/00/00text0 +backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text0.moz-backup +FINISH PATCH Contents/Resources/0/00/00png0.png +backup_restore: backup file doesn't exist: Contents/Resources/0/00/00png0.png.moz-backup +FINISH PATCH Contents/MacOS/exe0.exe +backup_restore: backup file doesn't exist: Contents/MacOS/exe0.exe.moz-backup +FINISH ADD Contents/Resources/2/20/20text0 +backup_restore: backup file doesn't exist: Contents/Resources/2/20/20text0.moz-backup +FINISH ADD Contents/Resources/2/20/20png0.png +backup_restore: backup file doesn't exist: Contents/Resources/2/20/20png0.png.moz-backup +FINISH ADD Contents/Resources/0/00/00text2 +backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text2.moz-backup +FINISH REMOVEFILE Contents/Resources/1/10/10text0 +backup_restore: backup file doesn't exist: Contents/Resources/1/10/10text0.moz-backup +FINISH REMOVEFILE Contents/Resources/0/00/00text1 +backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text1.moz-backup +FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0 +backup_restore: backup file doesn't exist: Contents/Resources/9/97/970/97xtext0.moz-backup +FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1 +backup_restore: backup file doesn't exist: Contents/Resources/9/97/970/97xtext1.moz-backup +FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0 +backup_restore: backup file doesn't exist: Contents/Resources/9/97/971/97xtext0.moz-backup +FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1 +backup_restore: backup file doesn't exist: Contents/Resources/9/97/971/97xtext1.moz-backup +FINISH REMOVEFILE Contents/Resources/9/96/96text0 +backup_restore: backup file doesn't exist: Contents/Resources/9/96/96text0.moz-backup +FINISH REMOVEFILE Contents/Resources/9/96/96text1 +backup_restore: backup file doesn't exist: Contents/Resources/9/96/96text1.moz-backup +FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0 +backup_restore: backup file doesn't exist: Contents/Resources/8/87/870/87xtext0.moz-backup +FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1 +backup_restore: backup file doesn't exist: Contents/Resources/8/87/870/87xtext1.moz-backup +FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0 +backup_restore: backup file doesn't exist: Contents/Resources/8/87/871/87xtext0.moz-backup +FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1 +backup_restore: backup file doesn't exist: Contents/Resources/8/87/871/87xtext1.moz-backup +FINISH REMOVEFILE Contents/Resources/8/86/86text0 +backup_restore: backup file doesn't exist: Contents/Resources/8/86/86text0.moz-backup +FINISH REMOVEFILE Contents/Resources/8/86/86text1 +backup_restore: backup file doesn't exist: Contents/Resources/8/86/86text1.moz-backup +FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe +backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtest.exe.moz-backup +FINISH REMOVEFILE Contents/Resources/7/70/7xtext0 +backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtext0.moz-backup +FINISH REMOVEFILE Contents/Resources/7/70/7xtext1 +backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtext1.moz-backup +FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe +backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtest.exe.moz-backup +FINISH REMOVEFILE Contents/Resources/7/71/7xtext0 +backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtext0.moz-backup +FINISH REMOVEFILE Contents/Resources/7/71/7xtext1 +backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtext1.moz-backup +FINISH REMOVEFILE Contents/Resources/7/7text0 +backup_restore: backup file doesn't exist: Contents/Resources/7/7text0.moz-backup +FINISH REMOVEFILE Contents/Resources/7/7text1 +backup_restore: backup file doesn't exist: Contents/Resources/7/7text1.moz-backup +FINISH REMOVEFILE Contents/Resources/5/5text1 +backup_restore: backup file doesn't exist: Contents/Resources/5/5text1.moz-backup +FINISH REMOVEFILE Contents/Resources/5/5text0 +backup_restore: backup file doesn't exist: Contents/Resources/5/5text0.moz-backup +FINISH REMOVEFILE Contents/Resources/5/5test.exe +backup_restore: backup file doesn't exist: Contents/Resources/5/5test.exe.moz-backup +FINISH REMOVEFILE Contents/Resources/5/5text0 +backup_restore: backup file doesn't exist: Contents/Resources/5/5text0.moz-backup +FINISH REMOVEFILE Contents/Resources/5/5text1 +backup_restore: backup file doesn't exist: Contents/Resources/5/5text1.moz-backup +FINISH REMOVEFILE Contents/Resources/4/4text1 +backup_restore: backup file doesn't exist: Contents/Resources/4/4text1.moz-backup +FINISH REMOVEFILE Contents/Resources/4/4text0 +backup_restore: backup file doesn't exist: Contents/Resources/4/4text0.moz-backup +FINISH REMOVEFILE Contents/Resources/3/3text1 +backup_restore: backup file doesn't exist: Contents/Resources/3/3text1.moz-backup +FINISH REMOVEFILE Contents/Resources/3/3text0 +backup_restore: backup file doesn't exist: Contents/Resources/3/3text0.moz-backup +failed: 2 +calling QuitProgressUI diff --git a/toolkit/mozapps/update/tests/data/partial_log_failure_win b/toolkit/mozapps/update/tests/data/partial_log_failure_win new file mode 100644 index 000000000..e3d683dc1 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_log_failure_win @@ -0,0 +1,192 @@ +UPDATE TYPE partial +PREPARE ADD searchplugins/searchpluginstext0 +PREPARE PATCH searchplugins/searchpluginspng1.png +PREPARE PATCH searchplugins/searchpluginspng0.png +PREPARE ADD precomplete +PREPARE PATCH exe0.exe +PREPARE ADD distribution/extensions/extensions1/extensions1text0 +PREPARE PATCH distribution/extensions/extensions1/extensions1png1.png +PREPARE PATCH distribution/extensions/extensions1/extensions1png0.png +PREPARE ADD distribution/extensions/extensions0/extensions0text0 +PREPARE PATCH distribution/extensions/extensions0/extensions0png1.png +PREPARE PATCH distribution/extensions/extensions0/extensions0png0.png +PREPARE PATCH 0/0exe0.exe +PREPARE ADD 0/00/00text0 +PREPARE PATCH 0/00/00png0.png +PREPARE ADD 2/20/20text0 +PREPARE ADD 2/20/20png0.png +PREPARE ADD 0/00/00text2 +PREPARE REMOVEFILE 1/10/10text0 +PREPARE REMOVEFILE 0/00/00text1 +PREPARE REMOVEDIR 9/99/ +PREPARE REMOVEDIR 9/99/ +PREPARE REMOVEDIR 9/98/ +PREPARE REMOVEFILE 9/97/970/97xtext0 +PREPARE REMOVEFILE 9/97/970/97xtext1 +PREPARE REMOVEDIR 9/97/970/ +PREPARE REMOVEFILE 9/97/971/97xtext0 +PREPARE REMOVEFILE 9/97/971/97xtext1 +PREPARE REMOVEDIR 9/97/971/ +PREPARE REMOVEDIR 9/97/ +PREPARE REMOVEFILE 9/96/96text0 +PREPARE REMOVEFILE 9/96/96text1 +PREPARE REMOVEDIR 9/96/ +PREPARE REMOVEDIR 9/95/ +PREPARE REMOVEDIR 9/95/ +PREPARE REMOVEDIR 9/94/ +PREPARE REMOVEDIR 9/94/ +PREPARE REMOVEDIR 9/93/ +PREPARE REMOVEDIR 9/92/ +PREPARE REMOVEDIR 9/91/ +PREPARE REMOVEDIR 9/90/ +PREPARE REMOVEDIR 9/90/ +PREPARE REMOVEDIR 8/89/ +PREPARE REMOVEDIR 8/89/ +PREPARE REMOVEDIR 8/88/ +PREPARE REMOVEFILE 8/87/870/87xtext0 +PREPARE REMOVEFILE 8/87/870/87xtext1 +PREPARE REMOVEDIR 8/87/870/ +PREPARE REMOVEFILE 8/87/871/87xtext0 +PREPARE REMOVEFILE 8/87/871/87xtext1 +PREPARE REMOVEDIR 8/87/871/ +PREPARE REMOVEDIR 8/87/ +PREPARE REMOVEFILE 8/86/86text0 +PREPARE REMOVEFILE 8/86/86text1 +PREPARE REMOVEDIR 8/86/ +PREPARE REMOVEDIR 8/85/ +PREPARE REMOVEDIR 8/85/ +PREPARE REMOVEDIR 8/84/ +PREPARE REMOVEDIR 8/84/ +PREPARE REMOVEDIR 8/83/ +PREPARE REMOVEDIR 8/82/ +PREPARE REMOVEDIR 8/81/ +PREPARE REMOVEDIR 8/80/ +PREPARE REMOVEDIR 8/80/ +PREPARE REMOVEFILE 7/70/7xtest.exe +PREPARE REMOVEFILE 7/70/7xtext0 +PREPARE REMOVEFILE 7/70/7xtext1 +PREPARE REMOVEDIR 7/70/ +PREPARE REMOVEFILE 7/71/7xtest.exe +PREPARE REMOVEFILE 7/71/7xtext0 +PREPARE REMOVEFILE 7/71/7xtext1 +PREPARE REMOVEDIR 7/71/ +PREPARE REMOVEFILE 7/7text0 +PREPARE REMOVEFILE 7/7text1 +PREPARE REMOVEDIR 7/ +PREPARE REMOVEDIR 6/ +PREPARE REMOVEFILE 5/5text1 +PREPARE REMOVEFILE 5/5text0 +PREPARE REMOVEFILE 5/5test.exe +PREPARE REMOVEFILE 5/5text0 +PREPARE REMOVEFILE 5/5text1 +PREPARE REMOVEDIR 5/ +PREPARE REMOVEFILE 4/4text1 +PREPARE REMOVEFILE 4/4text0 +PREPARE REMOVEDIR 4/ +PREPARE REMOVEFILE 3/3text1 +PREPARE REMOVEFILE 3/3text0 +PREPARE REMOVEDIR 1/10/ +PREPARE REMOVEDIR 1/ +EXECUTE ADD searchplugins/searchpluginstext0 +EXECUTE PATCH searchplugins/searchpluginspng1.png +EXECUTE PATCH searchplugins/searchpluginspng0.png +EXECUTE ADD precomplete +EXECUTE PATCH exe0.exe +EXECUTE ADD distribution/extensions/extensions1/extensions1text0 +EXECUTE PATCH distribution/extensions/extensions1/extensions1png1.png +EXECUTE PATCH distribution/extensions/extensions1/extensions1png0.png +EXECUTE ADD distribution/extensions/extensions0/extensions0text0 +EXECUTE PATCH distribution/extensions/extensions0/extensions0png1.png +EXECUTE PATCH distribution/extensions/extensions0/extensions0png0.png +EXECUTE PATCH 0/0exe0.exe +LoadSourceFile: destination file size 776 does not match expected size 79872 +LoadSourceFile failed +### execution failed +FINISH ADD searchplugins/searchpluginstext0 +FINISH PATCH searchplugins/searchpluginspng1.png +FINISH PATCH searchplugins/searchpluginspng0.png +FINISH ADD precomplete +FINISH PATCH exe0.exe +FINISH ADD distribution/extensions/extensions1/extensions1text0 +backup_restore: backup file doesn't exist: distribution/extensions/extensions1/extensions1text0.moz-backup +FINISH PATCH distribution/extensions/extensions1/extensions1png1.png +FINISH PATCH distribution/extensions/extensions1/extensions1png0.png +FINISH ADD distribution/extensions/extensions0/extensions0text0 +FINISH PATCH distribution/extensions/extensions0/extensions0png1.png +FINISH PATCH distribution/extensions/extensions0/extensions0png0.png +FINISH PATCH 0/0exe0.exe +backup_restore: backup file doesn't exist: 0/0exe0.exe.moz-backup +FINISH ADD 0/00/00text0 +backup_restore: backup file doesn't exist: 0/00/00text0.moz-backup +FINISH PATCH 0/00/00png0.png +backup_restore: backup file doesn't exist: 0/00/00png0.png.moz-backup +FINISH ADD 2/20/20text0 +backup_restore: backup file doesn't exist: 2/20/20text0.moz-backup +FINISH ADD 2/20/20png0.png +backup_restore: backup file doesn't exist: 2/20/20png0.png.moz-backup +FINISH ADD 0/00/00text2 +backup_restore: backup file doesn't exist: 0/00/00text2.moz-backup +FINISH REMOVEFILE 1/10/10text0 +backup_restore: backup file doesn't exist: 1/10/10text0.moz-backup +FINISH REMOVEFILE 0/00/00text1 +backup_restore: backup file doesn't exist: 0/00/00text1.moz-backup +FINISH REMOVEFILE 9/97/970/97xtext0 +backup_restore: backup file doesn't exist: 9/97/970/97xtext0.moz-backup +FINISH REMOVEFILE 9/97/970/97xtext1 +backup_restore: backup file doesn't exist: 9/97/970/97xtext1.moz-backup +FINISH REMOVEFILE 9/97/971/97xtext0 +backup_restore: backup file doesn't exist: 9/97/971/97xtext0.moz-backup +FINISH REMOVEFILE 9/97/971/97xtext1 +backup_restore: backup file doesn't exist: 9/97/971/97xtext1.moz-backup +FINISH REMOVEFILE 9/96/96text0 +backup_restore: backup file doesn't exist: 9/96/96text0.moz-backup +FINISH REMOVEFILE 9/96/96text1 +backup_restore: backup file doesn't exist: 9/96/96text1.moz-backup +FINISH REMOVEFILE 8/87/870/87xtext0 +backup_restore: backup file doesn't exist: 8/87/870/87xtext0.moz-backup +FINISH REMOVEFILE 8/87/870/87xtext1 +backup_restore: backup file doesn't exist: 8/87/870/87xtext1.moz-backup +FINISH REMOVEFILE 8/87/871/87xtext0 +backup_restore: backup file doesn't exist: 8/87/871/87xtext0.moz-backup +FINISH REMOVEFILE 8/87/871/87xtext1 +backup_restore: backup file doesn't exist: 8/87/871/87xtext1.moz-backup +FINISH REMOVEFILE 8/86/86text0 +backup_restore: backup file doesn't exist: 8/86/86text0.moz-backup +FINISH REMOVEFILE 8/86/86text1 +backup_restore: backup file doesn't exist: 8/86/86text1.moz-backup +FINISH REMOVEFILE 7/70/7xtest.exe +backup_restore: backup file doesn't exist: 7/70/7xtest.exe.moz-backup +FINISH REMOVEFILE 7/70/7xtext0 +backup_restore: backup file doesn't exist: 7/70/7xtext0.moz-backup +FINISH REMOVEFILE 7/70/7xtext1 +backup_restore: backup file doesn't exist: 7/70/7xtext1.moz-backup +FINISH REMOVEFILE 7/71/7xtest.exe +backup_restore: backup file doesn't exist: 7/71/7xtest.exe.moz-backup +FINISH REMOVEFILE 7/71/7xtext0 +backup_restore: backup file doesn't exist: 7/71/7xtext0.moz-backup +FINISH REMOVEFILE 7/71/7xtext1 +backup_restore: backup file doesn't exist: 7/71/7xtext1.moz-backup +FINISH REMOVEFILE 7/7text0 +backup_restore: backup file doesn't exist: 7/7text0.moz-backup +FINISH REMOVEFILE 7/7text1 +backup_restore: backup file doesn't exist: 7/7text1.moz-backup +FINISH REMOVEFILE 5/5text1 +backup_restore: backup file doesn't exist: 5/5text1.moz-backup +FINISH REMOVEFILE 5/5text0 +backup_restore: backup file doesn't exist: 5/5text0.moz-backup +FINISH REMOVEFILE 5/5test.exe +backup_restore: backup file doesn't exist: 5/5test.exe.moz-backup +FINISH REMOVEFILE 5/5text0 +backup_restore: backup file doesn't exist: 5/5text0.moz-backup +FINISH REMOVEFILE 5/5text1 +backup_restore: backup file doesn't exist: 5/5text1.moz-backup +FINISH REMOVEFILE 4/4text1 +backup_restore: backup file doesn't exist: 4/4text1.moz-backup +FINISH REMOVEFILE 4/4text0 +backup_restore: backup file doesn't exist: 4/4text0.moz-backup +FINISH REMOVEFILE 3/3text1 +backup_restore: backup file doesn't exist: 3/3text1.moz-backup +FINISH REMOVEFILE 3/3text0 +backup_restore: backup file doesn't exist: 3/3text0.moz-backup +failed: 2 +calling QuitProgressUI diff --git a/toolkit/mozapps/update/tests/data/partial_log_success_mac b/toolkit/mozapps/update/tests/data/partial_log_success_mac new file mode 100644 index 000000000..fb5272ad2 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_log_success_mac @@ -0,0 +1,279 @@ +UPDATE TYPE partial +PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0 +PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng1.png +PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng0.png +PREPARE ADD Contents/Resources/precomplete +PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +PREPARE PATCH Contents/Resources/0/0exe0.exe +PREPARE ADD Contents/Resources/0/00/00text0 +PREPARE PATCH Contents/Resources/0/00/00png0.png +PREPARE PATCH Contents/MacOS/exe0.exe +PREPARE ADD Contents/Resources/2/20/20text0 +PREPARE ADD Contents/Resources/2/20/20png0.png +PREPARE ADD Contents/Resources/0/00/00text2 +PREPARE REMOVEFILE Contents/Resources/1/10/10text0 +PREPARE REMOVEFILE Contents/Resources/0/00/00text1 +PREPARE REMOVEDIR Contents/Resources/9/99/ +PREPARE REMOVEDIR Contents/Resources/9/99/ +PREPARE REMOVEDIR Contents/Resources/9/98/ +PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0 +PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1 +PREPARE REMOVEDIR Contents/Resources/9/97/970/ +PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0 +PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1 +PREPARE REMOVEDIR Contents/Resources/9/97/971/ +PREPARE REMOVEDIR Contents/Resources/9/97/ +PREPARE REMOVEFILE Contents/Resources/9/96/96text0 +PREPARE REMOVEFILE Contents/Resources/9/96/96text1 +PREPARE REMOVEDIR Contents/Resources/9/96/ +PREPARE REMOVEDIR Contents/Resources/9/95/ +PREPARE REMOVEDIR Contents/Resources/9/95/ +PREPARE REMOVEDIR Contents/Resources/9/94/ +PREPARE REMOVEDIR Contents/Resources/9/94/ +PREPARE REMOVEDIR Contents/Resources/9/93/ +PREPARE REMOVEDIR Contents/Resources/9/92/ +PREPARE REMOVEDIR Contents/Resources/9/91/ +PREPARE REMOVEDIR Contents/Resources/9/90/ +PREPARE REMOVEDIR Contents/Resources/9/90/ +PREPARE REMOVEDIR Contents/Resources/8/89/ +PREPARE REMOVEDIR Contents/Resources/8/89/ +PREPARE REMOVEDIR Contents/Resources/8/88/ +PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0 +PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1 +PREPARE REMOVEDIR Contents/Resources/8/87/870/ +PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0 +PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1 +PREPARE REMOVEDIR Contents/Resources/8/87/871/ +PREPARE REMOVEDIR Contents/Resources/8/87/ +PREPARE REMOVEFILE Contents/Resources/8/86/86text0 +PREPARE REMOVEFILE Contents/Resources/8/86/86text1 +PREPARE REMOVEDIR Contents/Resources/8/86/ +PREPARE REMOVEDIR Contents/Resources/8/85/ +PREPARE REMOVEDIR Contents/Resources/8/85/ +PREPARE REMOVEDIR Contents/Resources/8/84/ +PREPARE REMOVEDIR Contents/Resources/8/84/ +PREPARE REMOVEDIR Contents/Resources/8/83/ +PREPARE REMOVEDIR Contents/Resources/8/82/ +PREPARE REMOVEDIR Contents/Resources/8/81/ +PREPARE REMOVEDIR Contents/Resources/8/80/ +PREPARE REMOVEDIR Contents/Resources/8/80/ +PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe +PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0 +PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1 +PREPARE REMOVEDIR Contents/Resources/7/70/ +PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe +PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0 +PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1 +PREPARE REMOVEDIR Contents/Resources/7/71/ +PREPARE REMOVEFILE Contents/Resources/7/7text0 +PREPARE REMOVEFILE Contents/Resources/7/7text1 +PREPARE REMOVEDIR Contents/Resources/7/ +PREPARE REMOVEDIR Contents/Resources/6/ +PREPARE REMOVEFILE Contents/Resources/5/5text1 +PREPARE REMOVEFILE Contents/Resources/5/5text0 +PREPARE REMOVEFILE Contents/Resources/5/5test.exe +PREPARE REMOVEFILE Contents/Resources/5/5text0 +PREPARE REMOVEFILE Contents/Resources/5/5text1 +PREPARE REMOVEDIR Contents/Resources/5/ +PREPARE REMOVEFILE Contents/Resources/4/4text1 +PREPARE REMOVEFILE Contents/Resources/4/4text0 +PREPARE REMOVEDIR Contents/Resources/4/ +PREPARE REMOVEFILE Contents/Resources/3/3text1 +PREPARE REMOVEFILE Contents/Resources/3/3text0 +PREPARE REMOVEDIR Contents/Resources/1/10/ +PREPARE REMOVEDIR Contents/Resources/1/ +EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0 +EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng1.png +EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng0.png +EXECUTE ADD Contents/Resources/precomplete +EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +EXECUTE PATCH Contents/Resources/0/0exe0.exe +EXECUTE ADD Contents/Resources/0/00/00text0 +EXECUTE PATCH Contents/Resources/0/00/00png0.png +EXECUTE PATCH Contents/MacOS/exe0.exe +EXECUTE ADD Contents/Resources/2/20/20text0 +EXECUTE ADD Contents/Resources/2/20/20png0.png +EXECUTE ADD Contents/Resources/0/00/00text2 +EXECUTE REMOVEFILE Contents/Resources/1/10/10text0 +EXECUTE REMOVEFILE Contents/Resources/0/00/00text1 +EXECUTE REMOVEDIR Contents/Resources/9/99/ +EXECUTE REMOVEDIR Contents/Resources/9/99/ +EXECUTE REMOVEDIR Contents/Resources/9/98/ +EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext0 +EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext1 +EXECUTE REMOVEDIR Contents/Resources/9/97/970/ +EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext0 +EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext1 +EXECUTE REMOVEDIR Contents/Resources/9/97/971/ +EXECUTE REMOVEDIR Contents/Resources/9/97/ +EXECUTE REMOVEFILE Contents/Resources/9/96/96text0 +EXECUTE REMOVEFILE Contents/Resources/9/96/96text1 +EXECUTE REMOVEDIR Contents/Resources/9/96/ +EXECUTE REMOVEDIR Contents/Resources/9/95/ +EXECUTE REMOVEDIR Contents/Resources/9/95/ +EXECUTE REMOVEDIR Contents/Resources/9/94/ +EXECUTE REMOVEDIR Contents/Resources/9/94/ +EXECUTE REMOVEDIR Contents/Resources/9/93/ +EXECUTE REMOVEDIR Contents/Resources/9/92/ +EXECUTE REMOVEDIR Contents/Resources/9/91/ +EXECUTE REMOVEDIR Contents/Resources/9/90/ +EXECUTE REMOVEDIR Contents/Resources/9/90/ +EXECUTE REMOVEDIR Contents/Resources/8/89/ +EXECUTE REMOVEDIR Contents/Resources/8/89/ +EXECUTE REMOVEDIR Contents/Resources/8/88/ +EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext0 +EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext1 +EXECUTE REMOVEDIR Contents/Resources/8/87/870/ +EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext0 +EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext1 +EXECUTE REMOVEDIR Contents/Resources/8/87/871/ +EXECUTE REMOVEDIR Contents/Resources/8/87/ +EXECUTE REMOVEFILE Contents/Resources/8/86/86text0 +EXECUTE REMOVEFILE Contents/Resources/8/86/86text1 +EXECUTE REMOVEDIR Contents/Resources/8/86/ +EXECUTE REMOVEDIR Contents/Resources/8/85/ +EXECUTE REMOVEDIR Contents/Resources/8/85/ +EXECUTE REMOVEDIR Contents/Resources/8/84/ +EXECUTE REMOVEDIR Contents/Resources/8/84/ +EXECUTE REMOVEDIR Contents/Resources/8/83/ +EXECUTE REMOVEDIR Contents/Resources/8/82/ +EXECUTE REMOVEDIR Contents/Resources/8/81/ +EXECUTE REMOVEDIR Contents/Resources/8/80/ +EXECUTE REMOVEDIR Contents/Resources/8/80/ +EXECUTE REMOVEFILE Contents/Resources/7/70/7xtest.exe +EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext0 +EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext1 +EXECUTE REMOVEDIR Contents/Resources/7/70/ +EXECUTE REMOVEFILE Contents/Resources/7/71/7xtest.exe +EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext0 +EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext1 +EXECUTE REMOVEDIR Contents/Resources/7/71/ +EXECUTE REMOVEFILE Contents/Resources/7/7text0 +EXECUTE REMOVEFILE Contents/Resources/7/7text1 +EXECUTE REMOVEDIR Contents/Resources/7/ +EXECUTE REMOVEDIR Contents/Resources/6/ +EXECUTE REMOVEFILE Contents/Resources/5/5text1 +EXECUTE REMOVEFILE Contents/Resources/5/5text0 +EXECUTE REMOVEFILE Contents/Resources/5/5test.exe +EXECUTE REMOVEFILE Contents/Resources/5/5text0 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEFILE Contents/Resources/5/5text1 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEDIR Contents/Resources/5/ +EXECUTE REMOVEFILE Contents/Resources/4/4text1 +EXECUTE REMOVEFILE Contents/Resources/4/4text0 +EXECUTE REMOVEDIR Contents/Resources/4/ +EXECUTE REMOVEFILE Contents/Resources/3/3text1 +EXECUTE REMOVEFILE Contents/Resources/3/3text0 +EXECUTE REMOVEDIR Contents/Resources/1/10/ +EXECUTE REMOVEDIR Contents/Resources/1/ +FINISH ADD Contents/Resources/searchplugins/searchpluginstext0 +FINISH PATCH Contents/Resources/searchplugins/searchpluginspng1.png +FINISH PATCH Contents/Resources/searchplugins/searchpluginspng0.png +FINISH ADD Contents/Resources/precomplete +FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0 +FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png +FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png +FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0 +FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png +FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png +FINISH PATCH Contents/Resources/0/0exe0.exe +FINISH ADD Contents/Resources/0/00/00text0 +FINISH PATCH Contents/Resources/0/00/00png0.png +FINISH PATCH Contents/MacOS/exe0.exe +FINISH ADD Contents/Resources/2/20/20text0 +FINISH ADD Contents/Resources/2/20/20png0.png +FINISH ADD Contents/Resources/0/00/00text2 +FINISH REMOVEFILE Contents/Resources/1/10/10text0 +FINISH REMOVEFILE Contents/Resources/0/00/00text1 +FINISH REMOVEDIR Contents/Resources/9/99/ +FINISH REMOVEDIR Contents/Resources/9/99/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/9/98/ +FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0 +FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1 +FINISH REMOVEDIR Contents/Resources/9/97/970/ +FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0 +FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1 +FINISH REMOVEDIR Contents/Resources/9/97/971/ +FINISH REMOVEDIR Contents/Resources/9/97/ +FINISH REMOVEFILE Contents/Resources/9/96/96text0 +FINISH REMOVEFILE Contents/Resources/9/96/96text1 +FINISH REMOVEDIR Contents/Resources/9/96/ +FINISH REMOVEDIR Contents/Resources/9/95/ +FINISH REMOVEDIR Contents/Resources/9/95/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/9/94/ +FINISH REMOVEDIR Contents/Resources/9/94/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/9/93/ +FINISH REMOVEDIR Contents/Resources/9/92/ +removing directory: Contents/Resources/9/92/, rv: 0 +FINISH REMOVEDIR Contents/Resources/9/91/ +removing directory: Contents/Resources/9/91/, rv: 0 +FINISH REMOVEDIR Contents/Resources/9/90/ +FINISH REMOVEDIR Contents/Resources/9/90/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/89/ +FINISH REMOVEDIR Contents/Resources/8/89/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/88/ +FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0 +FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1 +FINISH REMOVEDIR Contents/Resources/8/87/870/ +FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0 +FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1 +FINISH REMOVEDIR Contents/Resources/8/87/871/ +FINISH REMOVEDIR Contents/Resources/8/87/ +FINISH REMOVEFILE Contents/Resources/8/86/86text0 +FINISH REMOVEFILE Contents/Resources/8/86/86text1 +FINISH REMOVEDIR Contents/Resources/8/86/ +FINISH REMOVEDIR Contents/Resources/8/85/ +FINISH REMOVEDIR Contents/Resources/8/85/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/84/ +FINISH REMOVEDIR Contents/Resources/8/84/ +directory no longer exists; skipping +FINISH REMOVEDIR Contents/Resources/8/83/ +FINISH REMOVEDIR Contents/Resources/8/82/ +removing directory: Contents/Resources/8/82/, rv: 0 +FINISH REMOVEDIR Contents/Resources/8/81/ +removing directory: Contents/Resources/8/81/, rv: 0 +FINISH REMOVEDIR Contents/Resources/8/80/ +FINISH REMOVEDIR Contents/Resources/8/80/ +directory no longer exists; skipping +FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe +FINISH REMOVEFILE Contents/Resources/7/70/7xtext0 +FINISH REMOVEFILE Contents/Resources/7/70/7xtext1 +FINISH REMOVEDIR Contents/Resources/7/70/ +FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe +FINISH REMOVEFILE Contents/Resources/7/71/7xtext0 +FINISH REMOVEFILE Contents/Resources/7/71/7xtext1 +FINISH REMOVEDIR Contents/Resources/7/71/ +FINISH REMOVEFILE Contents/Resources/7/7text0 +FINISH REMOVEFILE Contents/Resources/7/7text1 +FINISH REMOVEDIR Contents/Resources/7/ +FINISH REMOVEDIR Contents/Resources/6/ +FINISH REMOVEFILE Contents/Resources/5/5text1 +FINISH REMOVEFILE Contents/Resources/5/5text0 +FINISH REMOVEFILE Contents/Resources/5/5test.exe +FINISH REMOVEDIR Contents/Resources/5/ +FINISH REMOVEFILE Contents/Resources/4/4text1 +FINISH REMOVEFILE Contents/Resources/4/4text0 +FINISH REMOVEDIR Contents/Resources/4/ +FINISH REMOVEFILE Contents/Resources/3/3text1 +FINISH REMOVEFILE Contents/Resources/3/3text0 +FINISH REMOVEDIR Contents/Resources/1/10/ +FINISH REMOVEDIR Contents/Resources/1/ +succeeded +calling QuitProgressUI diff --git a/toolkit/mozapps/update/tests/data/partial_log_success_win b/toolkit/mozapps/update/tests/data/partial_log_success_win new file mode 100644 index 000000000..1f5c4b3b4 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_log_success_win @@ -0,0 +1,279 @@ +UPDATE TYPE partial +PREPARE ADD searchplugins/searchpluginstext0 +PREPARE PATCH searchplugins/searchpluginspng1.png +PREPARE PATCH searchplugins/searchpluginspng0.png +PREPARE ADD precomplete +PREPARE PATCH exe0.exe +PREPARE ADD distribution/extensions/extensions1/extensions1text0 +PREPARE PATCH distribution/extensions/extensions1/extensions1png1.png +PREPARE PATCH distribution/extensions/extensions1/extensions1png0.png +PREPARE ADD distribution/extensions/extensions0/extensions0text0 +PREPARE PATCH distribution/extensions/extensions0/extensions0png1.png +PREPARE PATCH distribution/extensions/extensions0/extensions0png0.png +PREPARE PATCH 0/0exe0.exe +PREPARE ADD 0/00/00text0 +PREPARE PATCH 0/00/00png0.png +PREPARE ADD 2/20/20text0 +PREPARE ADD 2/20/20png0.png +PREPARE ADD 0/00/00text2 +PREPARE REMOVEFILE 1/10/10text0 +PREPARE REMOVEFILE 0/00/00text1 +PREPARE REMOVEDIR 9/99/ +PREPARE REMOVEDIR 9/99/ +PREPARE REMOVEDIR 9/98/ +PREPARE REMOVEFILE 9/97/970/97xtext0 +PREPARE REMOVEFILE 9/97/970/97xtext1 +PREPARE REMOVEDIR 9/97/970/ +PREPARE REMOVEFILE 9/97/971/97xtext0 +PREPARE REMOVEFILE 9/97/971/97xtext1 +PREPARE REMOVEDIR 9/97/971/ +PREPARE REMOVEDIR 9/97/ +PREPARE REMOVEFILE 9/96/96text0 +PREPARE REMOVEFILE 9/96/96text1 +PREPARE REMOVEDIR 9/96/ +PREPARE REMOVEDIR 9/95/ +PREPARE REMOVEDIR 9/95/ +PREPARE REMOVEDIR 9/94/ +PREPARE REMOVEDIR 9/94/ +PREPARE REMOVEDIR 9/93/ +PREPARE REMOVEDIR 9/92/ +PREPARE REMOVEDIR 9/91/ +PREPARE REMOVEDIR 9/90/ +PREPARE REMOVEDIR 9/90/ +PREPARE REMOVEDIR 8/89/ +PREPARE REMOVEDIR 8/89/ +PREPARE REMOVEDIR 8/88/ +PREPARE REMOVEFILE 8/87/870/87xtext0 +PREPARE REMOVEFILE 8/87/870/87xtext1 +PREPARE REMOVEDIR 8/87/870/ +PREPARE REMOVEFILE 8/87/871/87xtext0 +PREPARE REMOVEFILE 8/87/871/87xtext1 +PREPARE REMOVEDIR 8/87/871/ +PREPARE REMOVEDIR 8/87/ +PREPARE REMOVEFILE 8/86/86text0 +PREPARE REMOVEFILE 8/86/86text1 +PREPARE REMOVEDIR 8/86/ +PREPARE REMOVEDIR 8/85/ +PREPARE REMOVEDIR 8/85/ +PREPARE REMOVEDIR 8/84/ +PREPARE REMOVEDIR 8/84/ +PREPARE REMOVEDIR 8/83/ +PREPARE REMOVEDIR 8/82/ +PREPARE REMOVEDIR 8/81/ +PREPARE REMOVEDIR 8/80/ +PREPARE REMOVEDIR 8/80/ +PREPARE REMOVEFILE 7/70/7xtest.exe +PREPARE REMOVEFILE 7/70/7xtext0 +PREPARE REMOVEFILE 7/70/7xtext1 +PREPARE REMOVEDIR 7/70/ +PREPARE REMOVEFILE 7/71/7xtest.exe +PREPARE REMOVEFILE 7/71/7xtext0 +PREPARE REMOVEFILE 7/71/7xtext1 +PREPARE REMOVEDIR 7/71/ +PREPARE REMOVEFILE 7/7text0 +PREPARE REMOVEFILE 7/7text1 +PREPARE REMOVEDIR 7/ +PREPARE REMOVEDIR 6/ +PREPARE REMOVEFILE 5/5text1 +PREPARE REMOVEFILE 5/5text0 +PREPARE REMOVEFILE 5/5test.exe +PREPARE REMOVEFILE 5/5text0 +PREPARE REMOVEFILE 5/5text1 +PREPARE REMOVEDIR 5/ +PREPARE REMOVEFILE 4/4text1 +PREPARE REMOVEFILE 4/4text0 +PREPARE REMOVEDIR 4/ +PREPARE REMOVEFILE 3/3text1 +PREPARE REMOVEFILE 3/3text0 +PREPARE REMOVEDIR 1/10/ +PREPARE REMOVEDIR 1/ +EXECUTE ADD searchplugins/searchpluginstext0 +EXECUTE PATCH searchplugins/searchpluginspng1.png +EXECUTE PATCH searchplugins/searchpluginspng0.png +EXECUTE ADD precomplete +EXECUTE PATCH exe0.exe +EXECUTE ADD distribution/extensions/extensions1/extensions1text0 +EXECUTE PATCH distribution/extensions/extensions1/extensions1png1.png +EXECUTE PATCH distribution/extensions/extensions1/extensions1png0.png +EXECUTE ADD distribution/extensions/extensions0/extensions0text0 +EXECUTE PATCH distribution/extensions/extensions0/extensions0png1.png +EXECUTE PATCH distribution/extensions/extensions0/extensions0png0.png +EXECUTE PATCH 0/0exe0.exe +EXECUTE ADD 0/00/00text0 +EXECUTE PATCH 0/00/00png0.png +EXECUTE ADD 2/20/20text0 +EXECUTE ADD 2/20/20png0.png +EXECUTE ADD 0/00/00text2 +EXECUTE REMOVEFILE 1/10/10text0 +EXECUTE REMOVEFILE 0/00/00text1 +EXECUTE REMOVEDIR 9/99/ +EXECUTE REMOVEDIR 9/99/ +EXECUTE REMOVEDIR 9/98/ +EXECUTE REMOVEFILE 9/97/970/97xtext0 +EXECUTE REMOVEFILE 9/97/970/97xtext1 +EXECUTE REMOVEDIR 9/97/970/ +EXECUTE REMOVEFILE 9/97/971/97xtext0 +EXECUTE REMOVEFILE 9/97/971/97xtext1 +EXECUTE REMOVEDIR 9/97/971/ +EXECUTE REMOVEDIR 9/97/ +EXECUTE REMOVEFILE 9/96/96text0 +EXECUTE REMOVEFILE 9/96/96text1 +EXECUTE REMOVEDIR 9/96/ +EXECUTE REMOVEDIR 9/95/ +EXECUTE REMOVEDIR 9/95/ +EXECUTE REMOVEDIR 9/94/ +EXECUTE REMOVEDIR 9/94/ +EXECUTE REMOVEDIR 9/93/ +EXECUTE REMOVEDIR 9/92/ +EXECUTE REMOVEDIR 9/91/ +EXECUTE REMOVEDIR 9/90/ +EXECUTE REMOVEDIR 9/90/ +EXECUTE REMOVEDIR 8/89/ +EXECUTE REMOVEDIR 8/89/ +EXECUTE REMOVEDIR 8/88/ +EXECUTE REMOVEFILE 8/87/870/87xtext0 +EXECUTE REMOVEFILE 8/87/870/87xtext1 +EXECUTE REMOVEDIR 8/87/870/ +EXECUTE REMOVEFILE 8/87/871/87xtext0 +EXECUTE REMOVEFILE 8/87/871/87xtext1 +EXECUTE REMOVEDIR 8/87/871/ +EXECUTE REMOVEDIR 8/87/ +EXECUTE REMOVEFILE 8/86/86text0 +EXECUTE REMOVEFILE 8/86/86text1 +EXECUTE REMOVEDIR 8/86/ +EXECUTE REMOVEDIR 8/85/ +EXECUTE REMOVEDIR 8/85/ +EXECUTE REMOVEDIR 8/84/ +EXECUTE REMOVEDIR 8/84/ +EXECUTE REMOVEDIR 8/83/ +EXECUTE REMOVEDIR 8/82/ +EXECUTE REMOVEDIR 8/81/ +EXECUTE REMOVEDIR 8/80/ +EXECUTE REMOVEDIR 8/80/ +EXECUTE REMOVEFILE 7/70/7xtest.exe +EXECUTE REMOVEFILE 7/70/7xtext0 +EXECUTE REMOVEFILE 7/70/7xtext1 +EXECUTE REMOVEDIR 7/70/ +EXECUTE REMOVEFILE 7/71/7xtest.exe +EXECUTE REMOVEFILE 7/71/7xtext0 +EXECUTE REMOVEFILE 7/71/7xtext1 +EXECUTE REMOVEDIR 7/71/ +EXECUTE REMOVEFILE 7/7text0 +EXECUTE REMOVEFILE 7/7text1 +EXECUTE REMOVEDIR 7/ +EXECUTE REMOVEDIR 6/ +EXECUTE REMOVEFILE 5/5text1 +EXECUTE REMOVEFILE 5/5text0 +EXECUTE REMOVEFILE 5/5test.exe +EXECUTE REMOVEFILE 5/5text0 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEFILE 5/5text1 +file cannot be removed because it does not exist; skipping +EXECUTE REMOVEDIR 5/ +EXECUTE REMOVEFILE 4/4text1 +EXECUTE REMOVEFILE 4/4text0 +EXECUTE REMOVEDIR 4/ +EXECUTE REMOVEFILE 3/3text1 +EXECUTE REMOVEFILE 3/3text0 +EXECUTE REMOVEDIR 1/10/ +EXECUTE REMOVEDIR 1/ +FINISH ADD searchplugins/searchpluginstext0 +FINISH PATCH searchplugins/searchpluginspng1.png +FINISH PATCH searchplugins/searchpluginspng0.png +FINISH ADD precomplete +FINISH PATCH exe0.exe +FINISH ADD distribution/extensions/extensions1/extensions1text0 +FINISH PATCH distribution/extensions/extensions1/extensions1png1.png +FINISH PATCH distribution/extensions/extensions1/extensions1png0.png +FINISH ADD distribution/extensions/extensions0/extensions0text0 +FINISH PATCH distribution/extensions/extensions0/extensions0png1.png +FINISH PATCH distribution/extensions/extensions0/extensions0png0.png +FINISH PATCH 0/0exe0.exe +FINISH ADD 0/00/00text0 +FINISH PATCH 0/00/00png0.png +FINISH ADD 2/20/20text0 +FINISH ADD 2/20/20png0.png +FINISH ADD 0/00/00text2 +FINISH REMOVEFILE 1/10/10text0 +FINISH REMOVEFILE 0/00/00text1 +FINISH REMOVEDIR 9/99/ +FINISH REMOVEDIR 9/99/ +directory no longer exists; skipping +FINISH REMOVEDIR 9/98/ +FINISH REMOVEFILE 9/97/970/97xtext0 +FINISH REMOVEFILE 9/97/970/97xtext1 +FINISH REMOVEDIR 9/97/970/ +FINISH REMOVEFILE 9/97/971/97xtext0 +FINISH REMOVEFILE 9/97/971/97xtext1 +FINISH REMOVEDIR 9/97/971/ +FINISH REMOVEDIR 9/97/ +FINISH REMOVEFILE 9/96/96text0 +FINISH REMOVEFILE 9/96/96text1 +FINISH REMOVEDIR 9/96/ +FINISH REMOVEDIR 9/95/ +FINISH REMOVEDIR 9/95/ +directory no longer exists; skipping +FINISH REMOVEDIR 9/94/ +FINISH REMOVEDIR 9/94/ +directory no longer exists; skipping +FINISH REMOVEDIR 9/93/ +FINISH REMOVEDIR 9/92/ +removing directory: 9/92/, rv: 0 +FINISH REMOVEDIR 9/91/ +removing directory: 9/91/, rv: 0 +FINISH REMOVEDIR 9/90/ +FINISH REMOVEDIR 9/90/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/89/ +FINISH REMOVEDIR 8/89/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/88/ +FINISH REMOVEFILE 8/87/870/87xtext0 +FINISH REMOVEFILE 8/87/870/87xtext1 +FINISH REMOVEDIR 8/87/870/ +FINISH REMOVEFILE 8/87/871/87xtext0 +FINISH REMOVEFILE 8/87/871/87xtext1 +FINISH REMOVEDIR 8/87/871/ +FINISH REMOVEDIR 8/87/ +FINISH REMOVEFILE 8/86/86text0 +FINISH REMOVEFILE 8/86/86text1 +FINISH REMOVEDIR 8/86/ +FINISH REMOVEDIR 8/85/ +FINISH REMOVEDIR 8/85/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/84/ +FINISH REMOVEDIR 8/84/ +directory no longer exists; skipping +FINISH REMOVEDIR 8/83/ +FINISH REMOVEDIR 8/82/ +removing directory: 8/82/, rv: 0 +FINISH REMOVEDIR 8/81/ +removing directory: 8/81/, rv: 0 +FINISH REMOVEDIR 8/80/ +FINISH REMOVEDIR 8/80/ +directory no longer exists; skipping +FINISH REMOVEFILE 7/70/7xtest.exe +FINISH REMOVEFILE 7/70/7xtext0 +FINISH REMOVEFILE 7/70/7xtext1 +FINISH REMOVEDIR 7/70/ +FINISH REMOVEFILE 7/71/7xtest.exe +FINISH REMOVEFILE 7/71/7xtext0 +FINISH REMOVEFILE 7/71/7xtext1 +FINISH REMOVEDIR 7/71/ +FINISH REMOVEFILE 7/7text0 +FINISH REMOVEFILE 7/7text1 +FINISH REMOVEDIR 7/ +FINISH REMOVEDIR 6/ +FINISH REMOVEFILE 5/5text1 +FINISH REMOVEFILE 5/5text0 +FINISH REMOVEFILE 5/5test.exe +FINISH REMOVEDIR 5/ +FINISH REMOVEFILE 4/4text1 +FINISH REMOVEFILE 4/4text0 +FINISH REMOVEDIR 4/ +FINISH REMOVEFILE 3/3text1 +FINISH REMOVEFILE 3/3text0 +FINISH REMOVEDIR 1/10/ +FINISH REMOVEDIR 1/ +succeeded +calling QuitProgressUI diff --git a/toolkit/mozapps/update/tests/data/partial_mac.mar b/toolkit/mozapps/update/tests/data/partial_mac.mar new file mode 100644 index 000000000..5a702ed4a Binary files /dev/null and b/toolkit/mozapps/update/tests/data/partial_mac.mar differ diff --git a/toolkit/mozapps/update/tests/data/partial_precomplete b/toolkit/mozapps/update/tests/data/partial_precomplete new file mode 100644 index 000000000..3ec201463 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_precomplete @@ -0,0 +1,19 @@ +remove "searchplugins/searchpluginstext0" +remove "searchplugins/searchpluginspng1.png" +remove "searchplugins/searchpluginspng0.png" +remove "removed-files" +remove "precomplete" +remove "exe0.exe" +remove "2/20/20text0" +remove "2/20/20png0.png" +remove "0/0exe0.exe" +remove "0/00/00text2" +remove "0/00/00text0" +remove "0/00/00png0.png" +rmdir "searchplugins/" +rmdir "defaults/pref/" +rmdir "defaults/" +rmdir "2/20/" +rmdir "2/" +rmdir "0/00/" +rmdir "0/" diff --git a/toolkit/mozapps/update/tests/data/partial_precomplete_mac b/toolkit/mozapps/update/tests/data/partial_precomplete_mac new file mode 100644 index 000000000..c65b6e4e3 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_precomplete_mac @@ -0,0 +1,22 @@ +remove "Contents/Resources/searchplugins/searchpluginstext0" +remove "Contents/Resources/searchplugins/searchpluginspng1.png" +remove "Contents/Resources/searchplugins/searchpluginspng0.png" +remove "Contents/Resources/removed-files" +remove "Contents/Resources/precomplete" +remove "Contents/Resources/2/20/20text0" +remove "Contents/Resources/2/20/20png0.png" +remove "Contents/Resources/0/0exe0.exe" +remove "Contents/Resources/0/00/00text2" +remove "Contents/Resources/0/00/00text0" +remove "Contents/Resources/0/00/00png0.png" +remove "Contents/MacOS/exe0.exe" +rmdir "Contents/Resources/searchplugins/" +rmdir "Contents/Resources/defaults/pref/" +rmdir "Contents/Resources/defaults/" +rmdir "Contents/Resources/2/20/" +rmdir "Contents/Resources/2/" +rmdir "Contents/Resources/0/00/" +rmdir "Contents/Resources/0/" +rmdir "Contents/Resources/" +rmdir "Contents/MacOS/" +rmdir "Contents/" diff --git a/toolkit/mozapps/update/tests/data/partial_removed-files b/toolkit/mozapps/update/tests/data/partial_removed-files new file mode 100644 index 000000000..881311b82 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_removed-files @@ -0,0 +1,41 @@ +a/b/text0 +a/b/text1 +a/b/3/3text0 +a/b/3/3text1 +a/b/4/4exe0.exe +a/b/4/4text0 +a/b/4/4text1 +a/b/4/ +a/b/5/5text0 +a/b/5/5text1 +a/b/5/* +a/b/6/ +a/b/7/* +a/b/8/80/ +a/b/8/81/ +a/b/8/82/ +a/b/8/83/ +a/b/8/84/ +a/b/8/85/* +a/b/8/86/* +a/b/8/87/* +a/b/8/88/* +a/b/8/89/* +a/b/8/80/ +a/b/8/84/* +a/b/8/85/* +a/b/8/89/ +a/b/9/90/ +a/b/9/91/ +a/b/9/92/ +a/b/9/93/ +a/b/9/94/ +a/b/9/95/* +a/b/9/96/* +a/b/9/97/* +a/b/9/98/* +a/b/9/99/* +a/b/9/90/ +a/b/9/94/* +a/b/9/95/* +a/b/9/99/ diff --git a/toolkit/mozapps/update/tests/data/partial_removed-files_mac b/toolkit/mozapps/update/tests/data/partial_removed-files_mac new file mode 100644 index 000000000..955dc5b34 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_removed-files_mac @@ -0,0 +1,41 @@ +Contents/Resources/text0 +Contents/Resources/text1 +Contents/Resources/3/3text0 +Contents/Resources/3/3text1 +Contents/Resources/4/exe0.exe +Contents/Resources/4/4text0 +Contents/Resources/4/4text1 +Contents/Resources/4/ +Contents/Resources/5/5text0 +Contents/Resources/5/5text1 +Contents/Resources/5/* +Contents/Resources/6/ +Contents/Resources/7/* +Contents/Resources/8/80/ +Contents/Resources/8/81/ +Contents/Resources/8/82/ +Contents/Resources/8/83/ +Contents/Resources/8/84/ +Contents/Resources/8/85/* +Contents/Resources/8/86/* +Contents/Resources/8/87/* +Contents/Resources/8/88/* +Contents/Resources/8/89/* +Contents/Resources/8/80/ +Contents/Resources/8/84/* +Contents/Resources/8/85/* +Contents/Resources/8/89/ +Contents/Resources/9/90/ +Contents/Resources/9/91/ +Contents/Resources/9/92/ +Contents/Resources/9/93/ +Contents/Resources/9/94/ +Contents/Resources/9/95/* +Contents/Resources/9/96/* +Contents/Resources/9/97/* +Contents/Resources/9/98/* +Contents/Resources/9/99/* +Contents/Resources/9/90/ +Contents/Resources/9/94/* +Contents/Resources/9/95/* +Contents/Resources/9/99/ diff --git a/toolkit/mozapps/update/tests/data/partial_update_manifest b/toolkit/mozapps/update/tests/data/partial_update_manifest new file mode 100644 index 000000000..8d4e60ed2 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/partial_update_manifest @@ -0,0 +1,63 @@ +type "partial" +add "precomplete" +add "a/b/searchplugins/searchpluginstext0" +patch-if "a/b/searchplugins/searchpluginspng1.png" "a/b/searchplugins/searchpluginspng1.png.patch" "a/b/searchplugins/searchpluginspng1.png" +patch-if "a/b/searchplugins/searchpluginspng0.png" "a/b/searchplugins/searchpluginspng0.png.patch" "a/b/searchplugins/searchpluginspng0.png" +add-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1text0" +patch-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1png1.png.patch" "a/b/extensions/extensions1/extensions1png1.png" +patch-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1png0.png.patch" "a/b/extensions/extensions1/extensions1png0.png" +add-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0text0" +patch-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0png1.png.patch" "a/b/extensions/extensions0/extensions0png1.png" +patch-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0png0.png.patch" "a/b/extensions/extensions0/extensions0png0.png" +patch "a/b/exe0.exe.patch" "a/b/exe0.exe" +patch "a/b/0/0exe0.exe.patch" "a/b/0/0exe0.exe" +add "a/b/0/00/00text0" +patch "a/b/0/00/00png0.png.patch" "a/b/0/00/00png0.png" +add "a/b/2/20/20text0" +add "a/b/2/20/20png0.png" +add "a/b/0/00/00text2" +remove "a/b/1/10/10text0" +remove "a/b/0/00/00text1" +remove "a/b/text1" +remove "a/b/text0" +rmrfdir "a/b/9/99/" +rmdir "a/b/9/99/" +rmrfdir "a/b/9/98/" +rmrfdir "a/b/9/97/" +rmrfdir "a/b/9/96/" +rmrfdir "a/b/9/95/" +rmrfdir "a/b/9/95/" +rmrfdir "a/b/9/94/" +rmdir "a/b/9/94/" +rmdir "a/b/9/93/" +rmdir "a/b/9/92/" +rmdir "a/b/9/91/" +rmdir "a/b/9/90/" +rmdir "a/b/9/90/" +rmrfdir "a/b/8/89/" +rmdir "a/b/8/89/" +rmrfdir "a/b/8/88/" +rmrfdir "a/b/8/87/" +rmrfdir "a/b/8/86/" +rmrfdir "a/b/8/85/" +rmrfdir "a/b/8/85/" +rmrfdir "a/b/8/84/" +rmdir "a/b/8/84/" +rmdir "a/b/8/83/" +rmdir "a/b/8/82/" +rmdir "a/b/8/81/" +rmdir "a/b/8/80/" +rmdir "a/b/8/80/" +rmrfdir "a/b/7/" +rmdir "a/b/6/" +remove "a/b/5/5text1" +remove "a/b/5/5text0" +rmrfdir "a/b/5/" +remove "a/b/4/4text1" +remove "a/b/4/4text0" +remove "a/b/4/4exe0.exe" +rmdir "a/b/4/" +remove "a/b/3/3text1" +remove "a/b/3/3text0" +rmdir "a/b/1/10/" +rmdir "a/b/1/" diff --git a/toolkit/mozapps/update/tests/data/replace_log_success b/toolkit/mozapps/update/tests/data/replace_log_success new file mode 100644 index 000000000..323f1db41 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/replace_log_success @@ -0,0 +1,6 @@ +Performing a replace request +rename_file: proceeding to rename the directory +rename_file: proceeding to rename the directory +Now, remove the tmpDir +succeeded +calling QuitProgressUI diff --git a/toolkit/mozapps/update/tests/data/shared.js b/toolkit/mozapps/update/tests/data/shared.js new file mode 100644 index 000000000..e9a10da06 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/shared.js @@ -0,0 +1,632 @@ +/* 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/. */ + +/* Shared code for xpcshell and mochitests-chrome */ +/* eslint-disable no-undef */ + +Cu.import("resource://gre/modules/FileUtils.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const PREF_APP_UPDATE_AUTO = "app.update.auto"; +const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors"; +const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors"; +const PREF_APP_UPDATE_CHANNEL = "app.update.channel"; +const PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL = "app.update.download.backgroundInterval"; +const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; +const PREF_APP_UPDATE_IDLETIME = "app.update.idletime"; +const PREF_APP_UPDATE_LOG = "app.update.log"; +const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported"; +const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime"; +const PREF_APP_UPDATE_RETRYTIMEOUT = "app.update.socket.retryTimeout"; +const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled"; +const PREF_APP_UPDATE_SILENT = "app.update.silent"; +const PREF_APP_UPDATE_SOCKET_MAXERRORS = "app.update.socket.maxErrors"; +const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled"; +const PREF_APP_UPDATE_URL = "app.update.url"; +const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details"; + +const PREFBRANCH_APP_UPDATE_NEVER = "app.update.never."; + +const PREFBRANCH_APP_PARTNER = "app.partner."; +const PREF_DISTRIBUTION_ID = "distribution.id"; +const PREF_DISTRIBUTION_VERSION = "distribution.version"; +const PREF_TOOLKIT_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; + +const NS_APP_PROFILE_DIR_STARTUP = "ProfDS"; +const NS_APP_USER_PROFILE_50_DIR = "ProfD"; +const NS_GRE_DIR = "GreD"; +const NS_GRE_BIN_DIR = "GreBinD"; +const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD"; +const XRE_EXECUTABLE_FILE = "XREExeF"; +const XRE_UPDATE_ROOT_DIR = "UpdRootD"; + +const DIR_PATCH = "0"; +const DIR_TOBEDELETED = "tobedeleted"; +const DIR_UPDATES = "updates"; +const DIR_UPDATED = IS_MACOSX ? "Updated.app" : "updated"; + +const FILE_ACTIVE_UPDATE_XML = "active-update.xml"; +const FILE_APPLICATION_INI = "application.ini"; +const FILE_BACKUP_UPDATE_LOG = "backup-update.log"; +const FILE_LAST_UPDATE_LOG = "last-update.log"; +const FILE_UPDATE_SETTINGS_INI = "update-settings.ini"; +const FILE_UPDATE_SETTINGS_INI_BAK = "update-settings.ini.bak"; +const FILE_UPDATER_INI = "updater.ini"; +const FILE_UPDATES_XML = "updates.xml"; +const FILE_UPDATE_LOG = "update.log"; +const FILE_UPDATE_MAR = "update.mar"; +const FILE_UPDATE_STATUS = "update.status"; +const FILE_UPDATE_TEST = "update.test"; +const FILE_UPDATE_VERSION = "update.version"; + +const UPDATE_SETTINGS_CONTENTS = "[Settings]\n" + + "ACCEPTED_MAR_CHANNEL_IDS=xpcshell-test\n"; + +const PR_RDWR = 0x04; +const PR_CREATE_FILE = 0x08; +const PR_TRUNCATE = 0x20; + +const DEFAULT_UPDATE_VERSION = "999999.0"; + +var gChannel; + +/* import-globals-from ../data/sharedUpdateXML.js */ +Services.scriptloader.loadSubScript(DATA_URI_SPEC + "sharedUpdateXML.js", this); + +const PERMS_FILE = FileUtils.PERMS_FILE; +const PERMS_DIRECTORY = FileUtils.PERMS_DIRECTORY; + +const MODE_WRONLY = FileUtils.MODE_WRONLY; +const MODE_CREATE = FileUtils.MODE_CREATE; +const MODE_APPEND = FileUtils.MODE_APPEND; +const MODE_TRUNCATE = FileUtils.MODE_TRUNCATE; + +const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties"; +const gUpdateBundle = Services.strings.createBundle(URI_UPDATES_PROPERTIES); + +XPCOMUtils.defineLazyGetter(this, "gAUS", function test_gAUS() { + return Cc["@mozilla.org/updates/update-service;1"]. + getService(Ci.nsIApplicationUpdateService). + QueryInterface(Ci.nsITimerCallback). + QueryInterface(Ci.nsIObserver). + QueryInterface(Ci.nsIUpdateCheckListener); +}); + +XPCOMUtils.defineLazyServiceGetter(this, "gUpdateManager", + "@mozilla.org/updates/update-manager;1", + "nsIUpdateManager"); + +XPCOMUtils.defineLazyGetter(this, "gUpdateChecker", function test_gUC() { + return Cc["@mozilla.org/updates/update-checker;1"]. + createInstance(Ci.nsIUpdateChecker); +}); + +XPCOMUtils.defineLazyGetter(this, "gUP", function test_gUP() { + return Cc["@mozilla.org/updates/update-prompt;1"]. + createInstance(Ci.nsIUpdatePrompt); +}); + +XPCOMUtils.defineLazyGetter(this, "gDefaultPrefBranch", function test_gDPB() { + return Services.prefs.getDefaultBranch(null); +}); + +XPCOMUtils.defineLazyGetter(this, "gPrefRoot", function test_gPR() { + return Services.prefs.getBranch(null); +}); + +XPCOMUtils.defineLazyServiceGetter(this, "gEnv", + "@mozilla.org/process/environment;1", + "nsIEnvironment"); + +XPCOMUtils.defineLazyGetter(this, "gZipW", function test_gZipW() { + return Cc["@mozilla.org/zipwriter;1"]. + createInstance(Ci.nsIZipWriter); +}); + +/* Triggers post-update processing */ +function testPostUpdateProcessing() { + gAUS.observe(null, "test-post-update-processing", ""); +} + +/* Initializes the update service stub */ +function initUpdateServiceStub() { + Cc["@mozilla.org/updates/update-service-stub;1"]. + createInstance(Ci.nsISupports); +} + +/* Reloads the update metadata from disk */ +function reloadUpdateManagerData() { + gUpdateManager.QueryInterface(Ci.nsIObserver). + observe(null, "um-reload-update-data", ""); +} + +const observer = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "nsPref:changed" && aData == PREF_APP_UPDATE_CHANNEL) { + let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL); + if (channel != gChannel) { + debugDump("Changing channel from " + channel + " to " + gChannel); + gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, gChannel); + } + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) +}; + +/** + * Sets the app.update.channel preference. + * + * @param aChannel + * The update channel. + */ +function setUpdateChannel(aChannel) { + gChannel = aChannel; + debugDump("setting default pref " + PREF_APP_UPDATE_CHANNEL + " to " + gChannel); + gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, gChannel); + gPrefRoot.addObserver(PREF_APP_UPDATE_CHANNEL, observer, false); +} + +/** + * Sets the app.update.url default preference. + * + * @param aURL + * The update url. If not specified 'URL_HOST + "/update.xml"' will be + * used. + */ +function setUpdateURL(aURL) { + let url = aURL ? aURL : URL_HOST + "/update.xml"; + debugDump("setting " + PREF_APP_UPDATE_URL + " to " + url); + gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, url); +} + +/** + * Returns either the active or regular update database XML file. + * + * @param isActiveUpdate + * If true this will return the active-update.xml otherwise it will + * return the updates.xml file. + */ +function getUpdatesXMLFile(aIsActiveUpdate) { + let file = getUpdatesRootDir(); + file.append(aIsActiveUpdate ? FILE_ACTIVE_UPDATE_XML : FILE_UPDATES_XML); + return file; +} + +/** + * Writes the updates specified to either the active-update.xml or the + * updates.xml. + * + * @param aContent + * The updates represented as a string to write to the XML file. + * @param isActiveUpdate + * If true this will write to the active-update.xml otherwise it will + * write to the updates.xml file. + */ +function writeUpdatesToXMLFile(aContent, aIsActiveUpdate) { + writeFile(getUpdatesXMLFile(aIsActiveUpdate), aContent); +} + +/** + * Writes the current update operation/state to a file in the patch + * directory, indicating to the patching system that operations need + * to be performed. + * + * @param aStatus + * The status value to write. + */ +function writeStatusFile(aStatus) { + let file = getUpdatesPatchDir(); + file.append(FILE_UPDATE_STATUS); + writeFile(file, aStatus + "\n"); +} + +/** + * Writes the current update version to a file in the patch directory, + * indicating to the patching system the version of the update. + * + * @param aVersion + * The version value to write. + */ +function writeVersionFile(aVersion) { + let file = getUpdatesPatchDir(); + file.append(FILE_UPDATE_VERSION); + writeFile(file, aVersion + "\n"); +} + +/** + * Gets the root directory for the updates directory. + * + * @return nsIFile for the updates root directory. + */ +function getUpdatesRootDir() { + return Services.dirsvc.get(XRE_UPDATE_ROOT_DIR, Ci.nsIFile); +} + +/** + * Gets the updates directory. + * + * @return nsIFile for the updates directory. + */ +function getUpdatesDir() { + let dir = getUpdatesRootDir(); + dir.append(DIR_UPDATES); + return dir; +} + +/** + * Gets the directory for update patches. + * + * @return nsIFile for the updates directory. + */ +function getUpdatesPatchDir() { + let dir = getUpdatesDir(); + dir.append(DIR_PATCH); + return dir; +} + +/** + * Writes text to a file. This will replace existing text if the file exists + * and create the file if it doesn't exist. + * + * @param aFile + * The file to write to. Will be created if it doesn't exist. + * @param aText + * The text to write to the file. If there is existing text it will be + * replaced. + */ +function writeFile(aFile, aText) { + let fos = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + if (!aFile.exists()) { + aFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE); + } + fos.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0); + fos.write(aText, aText.length); + fos.close(); +} + +/** + * Reads the current update operation/state in the status file in the patch + * directory including the error code if it is present. + * + * @return The status value. + */ +function readStatusFile() { + let file = getUpdatesPatchDir(); + file.append(FILE_UPDATE_STATUS); + + if (!file.exists()) { + debugDump("update status file does not exists! Path: " + file.path); + return STATE_NONE; + } + + return readFile(file).split("\n")[0]; +} + +/** + * Reads the current update operation/state in the status file in the patch + * directory without the error code if it is present. + * + * @return The state value. + */ +function readStatusState() { + return readStatusFile().split(": ")[0]; +} + +/** + * Reads the current update operation/state in the status file in the patch + * directory with the error code. + * + * @return The state value. + */ +function readStatusFailedCode() { + return readStatusFile().split(": ")[1]; +} + +/** + * Reads text from a file and returns the string. + * + * @param aFile + * The file to read from. + * @return The string of text read from the file. + */ +function readFile(aFile) { + let fis = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + if (!aFile.exists()) { + return null; + } + // Specifying -1 for ioFlags will open the file with the default of PR_RDONLY. + // Specifying -1 for perm will open the file with the default of 0. + fis.init(aFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF); + let sis = Cc["@mozilla.org/scriptableinputstream;1"]. + createInstance(Ci.nsIScriptableInputStream); + sis.init(fis); + let text = sis.read(sis.available()); + sis.close(); + return text; +} + +/** + * Reads the binary contents of a file and returns it as a string. + * + * @param aFile + * The file to read from. + * @return The contents of the file as a string. + */ +function readFileBytes(aFile) { + debugDump("attempting to read file, path: " + aFile.path); + let fis = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + // Specifying -1 for ioFlags will open the file with the default of PR_RDONLY. + // Specifying -1 for perm will open the file with the default of 0. + fis.init(aFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF); + let bis = Cc["@mozilla.org/binaryinputstream;1"]. + createInstance(Ci.nsIBinaryInputStream); + bis.setInputStream(fis); + let data = []; + let count = fis.available(); + while (count > 0) { + let bytes = bis.readByteArray(Math.min(65535, count)); + data.push(String.fromCharCode.apply(null, bytes)); + count -= bytes.length; + if (bytes.length == 0) { + throw "Nothing read from input stream!"; + } + } + data.join(''); + fis.close(); + return data.toString(); +} + +/* Returns human readable status text from the updates.properties bundle */ +function getStatusText(aErrCode) { + return getString("check_error-" + aErrCode); +} + +/* Returns a string from the updates.properties bundle */ +function getString(aName) { + try { + return gUpdateBundle.GetStringFromName(aName); + } catch (e) { + } + return null; +} + +/** + * Gets the file extension for an nsIFile. + * + * @param aFile + * The file to get the file extension for. + * @return The file extension. + */ +function getFileExtension(aFile) { + return Services.io.newFileURI(aFile).QueryInterface(Ci.nsIURL). + fileExtension; +} + +/** + * Removes the updates.xml file, active-update.xml file, and all files and + * sub-directories in the updates directory except for the "0" sub-directory. + * This prevents some tests from failing due to files being left behind when the + * tests are interrupted. + */ +function removeUpdateDirsAndFiles() { + let file = getUpdatesXMLFile(true); + try { + if (file.exists()) { + file.remove(false); + } + } catch (e) { + logTestInfo("Unable to remove file. Path: " + file.path + + ", Exception: " + e); + } + + file = getUpdatesXMLFile(false); + try { + if (file.exists()) { + file.remove(false); + } + } catch (e) { + logTestInfo("Unable to remove file. Path: " + file.path + + ", Exception: " + e); + } + + // This fails sporadically on Mac OS X so wrap it in a try catch + let updatesDir = getUpdatesDir(); + try { + cleanUpdatesDir(updatesDir); + } catch (e) { + logTestInfo("Unable to remove files / directories from directory. Path: " + + updatesDir.path + ", Exception: " + e); + } +} + +/** + * Removes all files and sub-directories in the updates directory except for + * the "0" sub-directory. + * + * @param aDir + * nsIFile for the directory to be deleted. + */ +function cleanUpdatesDir(aDir) { + if (!aDir.exists()) { + return; + } + + let dirEntries = aDir.directoryEntries; + while (dirEntries.hasMoreElements()) { + let entry = dirEntries.getNext().QueryInterface(Ci.nsIFile); + + if (entry.isDirectory()) { + if (entry.leafName == DIR_PATCH && entry.parent.leafName == DIR_UPDATES) { + cleanUpdatesDir(entry); + entry.permissions = PERMS_DIRECTORY; + } else { + try { + entry.remove(true); + return; + } catch (e) { + } + cleanUpdatesDir(entry); + entry.permissions = PERMS_DIRECTORY; + try { + entry.remove(true); + } catch (e) { + logTestInfo("cleanUpdatesDir: unable to remove directory. Path: " + + entry.path + ", Exception: " + e); + throw (e); + } + } + } else { + entry.permissions = PERMS_FILE; + try { + entry.remove(false); + } catch (e) { + logTestInfo("cleanUpdatesDir: unable to remove file. Path: " + + entry.path + ", Exception: " + e); + throw (e); + } + } + } +} + +/** + * Deletes a directory and its children. First it tries nsIFile::Remove(true). + * If that fails it will fall back to recursing, setting the appropriate + * permissions, and deleting the current entry. + * + * @param aDir + * nsIFile for the directory to be deleted. + */ +function removeDirRecursive(aDir) { + if (!aDir.exists()) { + return; + } + + try { + debugDump("attempting to remove directory. Path: " + aDir.path); + aDir.remove(true); + return; + } catch (e) { + logTestInfo("non-fatal error removing directory. Exception: " + e); + } + + let dirEntries = aDir.directoryEntries; + while (dirEntries.hasMoreElements()) { + let entry = dirEntries.getNext().QueryInterface(Ci.nsIFile); + + if (entry.isDirectory()) { + removeDirRecursive(entry); + } else { + entry.permissions = PERMS_FILE; + try { + debugDump("attempting to remove file. Path: " + entry.path); + entry.remove(false); + } catch (e) { + logTestInfo("error removing file. Exception: " + e); + throw (e); + } + } + } + + aDir.permissions = PERMS_DIRECTORY; + try { + debugDump("attempting to remove directory. Path: " + aDir.path); + aDir.remove(true); + } catch (e) { + logTestInfo("error removing directory. Exception: " + e); + throw (e); + } +} + +/** + * Returns the directory for the currently running process. This is used to + * clean up after the tests and to locate the active-update.xml and updates.xml + * files. + * + * @return nsIFile for the current process directory. + */ +function getCurrentProcessDir() { + return Services.dirsvc.get(NS_XPCOM_CURRENT_PROCESS_DIR, Ci.nsIFile); +} + +/** + * Gets the application base directory. + * + * @return nsIFile object for the application base directory. + */ +function getAppBaseDir() { + return Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent; +} + +/** + * Returns the Gecko Runtime Engine directory where files other than executable + * binaries are located. On Mac OS X this will be /Contents/Resources/ + * and the installation directory on all other platforms. + * + * @return nsIFile for the Gecko Runtime Engine directory. + */ +function getGREDir() { + return Services.dirsvc.get(NS_GRE_DIR, Ci.nsIFile); +} + +/** + * Returns the Gecko Runtime Engine Binary directory where the executable + * binaries are located such as the updater binary (Windows and Linux) or + * updater package (Mac OS X). On Mac OS X this will be + * /Contents/MacOS/ and the installation directory on all other + * platforms. + * + * @return nsIFile for the Gecko Runtime Engine Binary directory. + */ +function getGREBinDir() { + return Services.dirsvc.get(NS_GRE_BIN_DIR, Ci.nsIFile); +} + +/** + * Logs TEST-INFO messages. + * + * @param aText + * The text to log. + * @param aCaller (optional) + * An optional Components.stack.caller. If not specified + * Components.stack.caller will be used. + */ +function logTestInfo(aText, aCaller) { + let caller = aCaller ? aCaller : Components.stack.caller; + let now = new Date(); + let hh = now.getHours(); + let mm = now.getMinutes(); + let ss = now.getSeconds(); + let ms = now.getMilliseconds(); + let time = (hh < 10 ? "0" + hh : hh) + ":" + + (mm < 10 ? "0" + mm : mm) + ":" + + (ss < 10 ? "0" + ss : ss) + ":"; + if (ms < 10) { + time += "00"; + } else if (ms < 100) { + time += "0"; + } + time += ms; + let msg = time + " | TEST-INFO | " + caller.filename + " | [" + caller.name + + " : " + caller.lineNumber + "] " + aText; + LOG_FUNCTION(msg); +} + +/** + * Logs TEST-INFO messages when DEBUG_AUS_TEST evaluates to true. + * + * @param aText + * The text to log. + * @param aCaller (optional) + * An optional Components.stack.caller. If not specified + * Components.stack.caller will be used. + */ +function debugDump(aText, aCaller) { + if (DEBUG_AUS_TEST) { + let caller = aCaller ? aCaller : Components.stack.caller; + logTestInfo(aText, caller); + } +} diff --git a/toolkit/mozapps/update/tests/data/sharedUpdateXML.js b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js new file mode 100644 index 000000000..3aa01eff4 --- /dev/null +++ b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js @@ -0,0 +1,364 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Helper functions for creating xml strings used by application update tests. + * + * !IMPORTANT - This file contains everything needed (along with dependencies) + * by the updates.sjs file used by the mochitest-chrome tests. Since xpcshell + * used by the http server is launched with -v 170 this file must not use + * features greater than JavaScript 1.7. + */ + +/* eslint-disable no-undef */ + +const FILE_SIMPLE_MAR = "simple.mar"; +const SIZE_SIMPLE_MAR = "1031"; +const MD5_HASH_SIMPLE_MAR = "1f8c038577bb6845d94ccec4999113ee"; +const SHA1_HASH_SIMPLE_MAR = "5d49a672c87f10f31d7e326349564a11272a028b"; +const SHA256_HASH_SIMPLE_MAR = "1aabbed5b1dd6e16e139afc5b43d479e254e0c26" + + "3c8fb9249c0a1bb93071c5fb"; +const SHA384_HASH_SIMPLE_MAR = "26615014ea034af32ef5651492d5f493f5a7a1a48522e" + + "d24c366442a5ec21d5ef02e23fb58d79729b8ca2f9541" + + "99dd53"; +const SHA512_HASH_SIMPLE_MAR = "922e5ae22081795f6e8d65a3c508715c9a314054179a8" + + "bbfe5f50dc23919ad89888291bc0a07586ab17dd0304a" + + "b5347473601127571c66f61f5080348e05c36b"; + +const STATE_NONE = "null"; +const STATE_DOWNLOADING = "downloading"; +const STATE_PENDING = "pending"; +const STATE_PENDING_SVC = "pending-service"; +const STATE_APPLYING = "applying"; +const STATE_APPLIED = "applied"; +const STATE_APPLIED_SVC = "applied-service"; +const STATE_SUCCEEDED = "succeeded"; +const STATE_DOWNLOAD_FAILED = "download-failed"; +const STATE_FAILED = "failed"; + +const LOADSOURCE_ERROR_WRONG_SIZE = 2; +const CRC_ERROR = 4; +const READ_ERROR = 6; +const WRITE_ERROR = 7; +const MAR_CHANNEL_MISMATCH_ERROR = 22; +const VERSION_DOWNGRADE_ERROR = 23; +const SERVICE_COULD_NOT_COPY_UPDATER = 49; +const SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR = 52; +const SERVICE_INVALID_APPLYTO_DIR_ERROR = 54; +const SERVICE_INVALID_INSTALL_DIR_PATH_ERROR = 55; +const SERVICE_INVALID_WORKING_DIR_PATH_ERROR = 56; +const INVALID_APPLYTO_DIR_STAGED_ERROR = 72; +const INVALID_APPLYTO_DIR_ERROR = 74; +const INVALID_INSTALL_DIR_PATH_ERROR = 75; +const INVALID_WORKING_DIR_PATH_ERROR = 76; +const INVALID_CALLBACK_PATH_ERROR = 77; +const INVALID_CALLBACK_DIR_ERROR = 78; + +const STATE_FAILED_DELIMETER = ": "; + +const STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE = + STATE_FAILED + STATE_FAILED_DELIMETER + LOADSOURCE_ERROR_WRONG_SIZE; +const STATE_FAILED_CRC_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + CRC_ERROR; +const STATE_FAILED_READ_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + READ_ERROR; +const STATE_FAILED_WRITE_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + WRITE_ERROR; +const STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + MAR_CHANNEL_MISMATCH_ERROR; +const STATE_FAILED_VERSION_DOWNGRADE_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + VERSION_DOWNGRADE_ERROR; +const STATE_FAILED_SERVICE_COULD_NOT_COPY_UPDATER = + STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_COULD_NOT_COPY_UPDATER +const STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR; +const STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_APPLYTO_DIR_ERROR; +const STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_INSTALL_DIR_PATH_ERROR; +const STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_WORKING_DIR_PATH_ERROR; +const STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_STAGED_ERROR; +const STATE_FAILED_INVALID_APPLYTO_DIR_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_ERROR; +const STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_INSTALL_DIR_PATH_ERROR; +const STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_WORKING_DIR_PATH_ERROR; +const STATE_FAILED_INVALID_CALLBACK_PATH_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_CALLBACK_PATH_ERROR; +const STATE_FAILED_INVALID_CALLBACK_DIR_ERROR = + STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_CALLBACK_DIR_ERROR; + +/** + * Constructs a string representing a remote update xml file. + * + * @param aUpdates + * The string representing the update elements. + * @return The string representing a remote update xml file. + */ +function getRemoteUpdatesXMLString(aUpdates) { + return "\n" + + "\n" + + aUpdates + + "\n"; +} + +/** + * Constructs a string representing an update element for a remote update xml + * file. See getUpdateString for parameter information not provided below. + * + * @param aPatches + * String representing the application update patches. + * @return The string representing an update element for an update xml file. + */ +function getRemoteUpdateString(aPatches, aType, aName, aDisplayVersion, + aAppVersion, aBuildID, aDetailsURL, aShowPrompt, + aShowNeverForVersion, aPromptWaitTime, + aBackgroundInterval, aCustom1, aCustom2) { + return getUpdateString(aType, aName, aDisplayVersion, aAppVersion, + aBuildID, aDetailsURL, aShowPrompt, + aShowNeverForVersion, aPromptWaitTime, + aBackgroundInterval, aCustom1, aCustom2) + ">\n" + + aPatches + + " \n"; +} + +/** + * Constructs a string representing a patch element for a remote update xml + * file. See getPatchString for parameter information not provided below. + * + * @return The string representing a patch element for a remote update xml file. + */ +function getRemotePatchString(aType, aURL, aHashFunction, aHashValue, aSize) { + return getPatchString(aType, aURL, aHashFunction, aHashValue, aSize) + + "/>\n"; +} + +/** + * Constructs a string representing a local update xml file. + * + * @param aUpdates + * The string representing the update elements. + * @return The string representing a local update xml file. + */ +function getLocalUpdatesXMLString(aUpdates) { + if (!aUpdates || aUpdates == "") { + return ""; + } + return ("" + + aUpdates + + "").replace(/>\s+\n*<'); +} + +/** + * Constructs a string representing an update element for a local update xml + * file. See getUpdateString for parameter information not provided below. + * + * @param aPatches + * String representing the application update patches. + * @param aServiceURL (optional) + * The update's xml url. + * If not specified it will default to 'http://test_service/'. + * @param aIsCompleteUpdate (optional) + * The string 'true' if this update was a complete update or the string + * 'false' if this update was a partial update. + * If not specified it will default to 'true'. + * @param aChannel (optional) + * The update channel name. + * If not specified it will default to the default preference value of + * app.update.channel. + * @param aForegroundDownload (optional) + * The string 'true' if this update was manually downloaded or the + * string 'false' if this update was automatically downloaded. + * If not specified it will default to 'true'. + * @param aPreviousAppVersion (optional) + * The application version prior to applying the update. + * If not specified it will not be present. + * @return The string representing an update element for an update xml file. + */ +function getLocalUpdateString(aPatches, aType, aName, aDisplayVersion, + aAppVersion, aBuildID, aDetailsURL, aServiceURL, + aInstallDate, aStatusText, aIsCompleteUpdate, + aChannel, aForegroundDownload, aShowPrompt, + aShowNeverForVersion, aPromptWaitTime, + aBackgroundInterval, aPreviousAppVersion, + aCustom1, aCustom2) { + let serviceURL = aServiceURL ? aServiceURL : "http://test_service/"; + let installDate = aInstallDate ? aInstallDate : "1238441400314"; + let statusText = aStatusText ? aStatusText : "Install Pending"; + let isCompleteUpdate = + typeof aIsCompleteUpdate == "string" ? aIsCompleteUpdate : "true"; + let channel = aChannel ? aChannel + : gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL); + let foregroundDownload = + typeof aForegroundDownload == "string" ? aForegroundDownload : "true"; + let previousAppVersion = aPreviousAppVersion ? "previousAppVersion=\"" + + aPreviousAppVersion + "\" " + : ""; + return getUpdateString(aType, aName, aDisplayVersion, aAppVersion, aBuildID, + aDetailsURL, aShowPrompt, aShowNeverForVersion, + aPromptWaitTime, aBackgroundInterval, aCustom1, aCustom2) + + " " + + previousAppVersion + + "serviceURL=\"" + serviceURL + "\" " + + "installDate=\"" + installDate + "\" " + + "statusText=\"" + statusText + "\" " + + "isCompleteUpdate=\"" + isCompleteUpdate + "\" " + + "channel=\"" + channel + "\" " + + "foregroundDownload=\"" + foregroundDownload + "\">" + + aPatches + + " "; +} + +/** + * Constructs a string representing a patch element for a local update xml file. + * See getPatchString for parameter information not provided below. + * + * @param aSelected (optional) + * Whether this patch is selected represented or not. The string 'true' + * denotes selected and the string 'false' denotes not selected. + * If not specified it will default to the string 'true'. + * @param aState (optional) + * The patch's state. + * If not specified it will default to STATE_SUCCEEDED. + * @return The string representing a patch element for a local update xml file. + */ +function getLocalPatchString(aType, aURL, aHashFunction, aHashValue, aSize, + aSelected, aState) { + let selected = typeof aSelected == "string" ? aSelected : "true"; + let state = aState ? aState : STATE_SUCCEEDED; + return getPatchString(aType, aURL, aHashFunction, aHashValue, aSize) + " " + + "selected=\"" + selected + "\" " + + "state=\"" + state + "\"/>\n"; +} + +/** + * Constructs a string representing an update element for a remote update xml + * file. + * + * @param aType (optional) + * The update's type which should be major or minor. If not specified it + * will default to 'major'. + * @param aName (optional) + * The update's name. + * If not specified it will default to 'App Update Test'. + * @param aDisplayVersion (optional) + * The update's display version. + * If not specified it will default to 'version #' where # is the value + * of DEFAULT_UPDATE_VERSION. + * @param aAppVersion (optional) + * The update's application version. + * If not specified it will default to the value of + * DEFAULT_UPDATE_VERSION. + * @param aBuildID (optional) + * The update's build id. + * If not specified it will default to '20080811053724'. + * @param aDetailsURL (optional) + * The update's details url. + * If not specified it will default to 'http://test_details/' due to due + * to bug 470244. + * @param aShowPrompt (optional) + * Whether to show the prompt for the update when auto update is + * enabled. + * If not specified it will not be present and the update service will + * default to false. + * @param aShowNeverForVersion (optional) + * Whether to show the 'No Thanks' button in the update prompt. + * If not specified it will not be present and the update service will + * default to false. + * @param aPromptWaitTime (optional) + * Override for the app.update.promptWaitTime preference. + * @param aBackgroundInterval (optional) + * Override for the app.update.download.backgroundInterval preference. + * @param aCustom1 (optional) + * A custom attribute name and attribute value to add to the xml. + * Example: custom1_attribute="custom1 value" + * If not specified it will not be present. + * @param aCustom2 (optional) + * A custom attribute name and attribute value to add to the xml. + * Example: custom2_attribute="custom2 value" + * If not specified it will not be present. + * @return The string representing an update element for an update xml file. + */ +function getUpdateString(aType, aName, aDisplayVersion, aAppVersion, aBuildID, + aDetailsURL, aShowPrompt, aShowNeverForVersion, + aPromptWaitTime, aBackgroundInterval, aCustom1, + aCustom2) { + let type = aType ? aType : "major"; + let name = aName ? aName : "App Update Test"; + let displayVersion = aDisplayVersion ? "displayVersion=\"" + + aDisplayVersion + "\" " + : ""; + let appVersion = "appVersion=\"" + + (aAppVersion ? aAppVersion : DEFAULT_UPDATE_VERSION) + + "\" "; + let buildID = aBuildID ? aBuildID : "20080811053724"; + // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244 +// let detailsURL = aDetailsURL ? "detailsURL=\"" + aDetailsURL + "\" " : ""; + let detailsURL = "detailsURL=\"" + + (aDetailsURL ? aDetailsURL + : "http://test_details/") + "\" "; + let showPrompt = aShowPrompt ? "showPrompt=\"" + aShowPrompt + "\" " : ""; + let showNeverForVersion = aShowNeverForVersion ? "showNeverForVersion=\"" + + aShowNeverForVersion + "\" " + : ""; + let promptWaitTime = aPromptWaitTime ? "promptWaitTime=\"" + aPromptWaitTime + + "\" " + : ""; + let backgroundInterval = aBackgroundInterval ? "backgroundInterval=\"" + + aBackgroundInterval + "\" " + : ""; + let custom1 = aCustom1 ? aCustom1 + " " : ""; + let custom2 = aCustom2 ? aCustom2 + " " : ""; + return " nul" : "> /dev/null 2>&1"; + +const LOG_FUNCTION = do_print; + +const gHTTPHandlerPath = "updates.xml"; + +// This default value will be overridden when using the http server. +var gURLData = URL_HOST + "/"; + +var gTestID; + +var gTestserver; + +var gRegisteredServiceCleanup; + +var gCheckFunc; +var gResponseBody; +var gResponseStatusCode = 200; +var gRequestURL; +var gUpdateCount; +var gUpdates; +var gStatusCode; +var gStatusText; +var gStatusResult; + +var gProcess; +var gAppTimer; +var gHandle; + +var gGREDirOrig; +var gGREBinDirOrig; +var gAppDirOrig; + +// Variables are used instead of contants so tests can override these values if +// necessary. +var gCallbackBinFile = "callback_app" + BIN_SUFFIX; +var gCallbackArgs = ["./", "callback.log", "Test Arg 2", "Test Arg 3"]; +var gPostUpdateBinFile = "postup_app" + BIN_SUFFIX; +var gSvcOriginalLogContents; +var gUseTestAppDir = true; +// Some update staging failures can remove the update. This allows tests to +// specify that the status file and the active update should not be checked +// after an update is staged. +var gStagingRemovedUpdate = false; + +var gTimeoutRuns = 0; +var gFileInUseTimeoutRuns = 0; + +// Environment related globals +var gShouldResetEnv = undefined; +var gAddedEnvXRENoWindowsCrashDialog = false; +var gEnvXPCOMDebugBreak; +var gEnvXPCOMMemLeakLog; +var gEnvDyldLibraryPath; +var gEnvLdLibraryPath; +var gASanOptions; + +// Set to true to log additional information for debugging. To log additional +// information for an individual test set DEBUG_AUS_TEST to true in the test's +// run_test function. +var DEBUG_AUS_TEST = true; + +const DATA_URI_SPEC = Services.io.newFileURI(do_get_file("../data", false)).spec; +/* import-globals-from ../data/shared.js */ +Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this); + +var gTestFiles = []; +var gTestDirs = []; + +// Common files for both successful and failed updates. +var gTestFilesCommon = [ + { + description: "Should never change", + fileName: FILE_UPDATE_SETTINGS_INI, + relPathDir: DIR_RESOURCES, + originalContents: UPDATE_SETTINGS_CONTENTS, + compareContents: UPDATE_SETTINGS_CONTENTS, + originalFile: null, + compareFile: null, + originalPerms: 0o767, + comparePerms: 0o767 + }, { + description: "Should never change", + fileName: "channel-prefs.js", + relPathDir: DIR_RESOURCES + "defaults/pref/", + originalContents: "ShouldNotBeReplaced\n", + compareContents: "ShouldNotBeReplaced\n", + originalFile: null, + compareFile: null, + originalPerms: 0o767, + comparePerms: 0o767 + }]; + + // Files for a complete successful update. This can be used for a complete + // failed update by calling setTestFilesAndDirsForFailure. +var gTestFilesCompleteSuccess = [ + { + description: "Added by update.manifest (add)", + fileName: "precomplete", + relPathDir: DIR_RESOURCES, + originalContents: null, + compareContents: null, + originalFile: FILE_PARTIAL_PRECOMPLETE, + compareFile: FILE_COMPLETE_PRECOMPLETE, + originalPerms: 0o666, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "searchpluginstext0", + relPathDir: DIR_RESOURCES + "searchplugins/", + originalContents: "ToBeReplacedWithFromComplete\n", + compareContents: "FromComplete\n", + originalFile: null, + compareFile: null, + originalPerms: 0o775, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "searchpluginspng1.png", + relPathDir: DIR_RESOURCES + "searchplugins/", + originalContents: null, + compareContents: null, + originalFile: null, + compareFile: "complete.png", + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "searchpluginspng0.png", + relPathDir: DIR_RESOURCES + "searchplugins/", + originalContents: null, + compareContents: null, + originalFile: "partial.png", + compareFile: "complete.png", + originalPerms: 0o666, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "removed-files", + relPathDir: DIR_RESOURCES, + originalContents: null, + compareContents: null, + originalFile: FILE_PARTIAL_REMOVEDFILES, + compareFile: FILE_COMPLETE_REMOVEDFILES, + originalPerms: 0o666, + comparePerms: 0o644 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions1text0", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/", + originalContents: null, + compareContents: "FromComplete\n", + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions1png1.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/", + originalContents: null, + compareContents: null, + originalFile: "partial.png", + compareFile: "complete.png", + originalPerms: 0o666, + comparePerms: 0o644 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions1png0.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/", + originalContents: null, + compareContents: null, + originalFile: null, + compareFile: "complete.png", + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions0text0", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/", + originalContents: "ToBeReplacedWithFromComplete\n", + compareContents: "FromComplete\n", + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions0png1.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/", + originalContents: null, + compareContents: null, + originalFile: null, + compareFile: "complete.png", + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions0png0.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/", + originalContents: null, + compareContents: null, + originalFile: null, + compareFile: "complete.png", + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "exe0.exe", + relPathDir: DIR_MACOS, + originalContents: null, + compareContents: null, + originalFile: FILE_HELPER_BIN, + compareFile: FILE_COMPLETE_EXE, + originalPerms: 0o777, + comparePerms: 0o755 + }, { + description: "Added by update.manifest (add)", + fileName: "10text0", + relPathDir: DIR_RESOURCES + "1/10/", + originalContents: "ToBeReplacedWithFromComplete\n", + compareContents: "FromComplete\n", + originalFile: null, + compareFile: null, + originalPerms: 0o767, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "0exe0.exe", + relPathDir: DIR_RESOURCES + "0/", + originalContents: null, + compareContents: null, + originalFile: FILE_HELPER_BIN, + compareFile: FILE_COMPLETE_EXE, + originalPerms: 0o777, + comparePerms: 0o755 + }, { + description: "Added by update.manifest (add)", + fileName: "00text1", + relPathDir: DIR_RESOURCES + "0/00/", + originalContents: "ToBeReplacedWithFromComplete\n", + compareContents: "FromComplete\n", + originalFile: null, + compareFile: null, + originalPerms: 0o677, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "00text0", + relPathDir: DIR_RESOURCES + "0/00/", + originalContents: "ToBeReplacedWithFromComplete\n", + compareContents: "FromComplete\n", + originalFile: null, + compareFile: null, + originalPerms: 0o775, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "00png0.png", + relPathDir: DIR_RESOURCES + "0/00/", + originalContents: null, + compareContents: null, + originalFile: null, + compareFile: "complete.png", + originalPerms: 0o776, + comparePerms: 0o644 + }, { + description: "Removed by precomplete (remove)", + fileName: "20text0", + relPathDir: DIR_RESOURCES + "2/20/", + originalContents: "ToBeDeleted\n", + compareContents: null, + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: null + }, { + description: "Removed by precomplete (remove)", + fileName: "20png0.png", + relPathDir: DIR_RESOURCES + "2/20/", + originalContents: "ToBeDeleted\n", + compareContents: null, + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: null + }]; + +// Concatenate the common files to the end of the array. +gTestFilesCompleteSuccess = gTestFilesCompleteSuccess.concat(gTestFilesCommon); + +// Files for a partial successful update. This can be used for a partial failed +// update by calling setTestFilesAndDirsForFailure. +var gTestFilesPartialSuccess = [ + { + description: "Added by update.manifest (add)", + fileName: "precomplete", + relPathDir: DIR_RESOURCES, + originalContents: null, + compareContents: null, + originalFile: FILE_COMPLETE_PRECOMPLETE, + compareFile: FILE_PARTIAL_PRECOMPLETE, + originalPerms: 0o666, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "searchpluginstext0", + relPathDir: DIR_RESOURCES + "searchplugins/", + originalContents: "ToBeReplacedWithFromPartial\n", + compareContents: "FromPartial\n", + originalFile: null, + compareFile: null, + originalPerms: 0o775, + comparePerms: 0o644 + }, { + description: "Patched by update.manifest if the file exists (patch-if)", + fileName: "searchpluginspng1.png", + relPathDir: DIR_RESOURCES + "searchplugins/", + originalContents: null, + compareContents: null, + originalFile: "complete.png", + compareFile: "partial.png", + originalPerms: 0o666, + comparePerms: 0o666 + }, { + description: "Patched by update.manifest if the file exists (patch-if)", + fileName: "searchpluginspng0.png", + relPathDir: DIR_RESOURCES + "searchplugins/", + originalContents: null, + compareContents: null, + originalFile: "complete.png", + compareFile: "partial.png", + originalPerms: 0o666, + comparePerms: 0o666 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions1text0", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/", + originalContents: null, + compareContents: "FromPartial\n", + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Patched by update.manifest if the parent directory exists (patch-if)", + fileName: "extensions1png1.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/", + originalContents: null, + compareContents: null, + originalFile: "complete.png", + compareFile: "partial.png", + originalPerms: 0o666, + comparePerms: 0o666 + }, { + description: "Patched by update.manifest if the parent directory exists (patch-if)", + fileName: "extensions1png0.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/", + originalContents: null, + compareContents: null, + originalFile: "complete.png", + compareFile: "partial.png", + originalPerms: 0o666, + comparePerms: 0o666 + }, { + description: "Added by update.manifest if the parent directory exists (add-if)", + fileName: "extensions0text0", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/", + originalContents: "ToBeReplacedWithFromPartial\n", + compareContents: "FromPartial\n", + originalFile: null, + compareFile: null, + originalPerms: 0o644, + comparePerms: 0o644 + }, { + description: "Patched by update.manifest if the parent directory exists (patch-if)", + fileName: "extensions0png1.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/", + originalContents: null, + compareContents: null, + originalFile: "complete.png", + compareFile: "partial.png", + originalPerms: 0o644, + comparePerms: 0o644 + }, { + description: "Patched by update.manifest if the parent directory exists (patch-if)", + fileName: "extensions0png0.png", + relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/", + originalContents: null, + compareContents: null, + originalFile: "complete.png", + compareFile: "partial.png", + originalPerms: 0o644, + comparePerms: 0o644 + }, { + description: "Patched by update.manifest (patch)", + fileName: "exe0.exe", + relPathDir: DIR_MACOS, + originalContents: null, + compareContents: null, + originalFile: FILE_COMPLETE_EXE, + compareFile: FILE_PARTIAL_EXE, + originalPerms: 0o755, + comparePerms: 0o755 + }, { + description: "Patched by update.manifest (patch)", + fileName: "0exe0.exe", + relPathDir: DIR_RESOURCES + "0/", + originalContents: null, + compareContents: null, + originalFile: FILE_COMPLETE_EXE, + compareFile: FILE_PARTIAL_EXE, + originalPerms: 0o755, + comparePerms: 0o755 + }, { + description: "Added by update.manifest (add)", + fileName: "00text0", + relPathDir: DIR_RESOURCES + "0/00/", + originalContents: "ToBeReplacedWithFromPartial\n", + compareContents: "FromPartial\n", + originalFile: null, + compareFile: null, + originalPerms: 0o644, + comparePerms: 0o644 + }, { + description: "Patched by update.manifest (patch)", + fileName: "00png0.png", + relPathDir: DIR_RESOURCES + "0/00/", + originalContents: null, + compareContents: null, + originalFile: "complete.png", + compareFile: "partial.png", + originalPerms: 0o666, + comparePerms: 0o666 + }, { + description: "Added by update.manifest (add)", + fileName: "20text0", + relPathDir: DIR_RESOURCES + "2/20/", + originalContents: null, + compareContents: "FromPartial\n", + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "20png0.png", + relPathDir: DIR_RESOURCES + "2/20/", + originalContents: null, + compareContents: null, + originalFile: null, + compareFile: "partial.png", + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Added by update.manifest (add)", + fileName: "00text2", + relPathDir: DIR_RESOURCES + "0/00/", + originalContents: null, + compareContents: "FromPartial\n", + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: 0o644 + }, { + description: "Removed by update.manifest (remove)", + fileName: "10text0", + relPathDir: DIR_RESOURCES + "1/10/", + originalContents: "ToBeDeleted\n", + compareContents: null, + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: null + }, { + description: "Removed by update.manifest (remove)", + fileName: "00text1", + relPathDir: DIR_RESOURCES + "0/00/", + originalContents: "ToBeDeleted\n", + compareContents: null, + originalFile: null, + compareFile: null, + originalPerms: null, + comparePerms: null + }]; + +// Concatenate the common files to the end of the array. +gTestFilesPartialSuccess = gTestFilesPartialSuccess.concat(gTestFilesCommon); + +var gTestDirsCommon = [ + { + relPathDir: DIR_RESOURCES + "3/", + dirRemoved: false, + files: ["3text0", "3text1"], + filesRemoved: true + }, { + relPathDir: DIR_RESOURCES + "4/", + dirRemoved: true, + files: ["4text0", "4text1"], + filesRemoved: true + }, { + relPathDir: DIR_RESOURCES + "5/", + dirRemoved: true, + files: ["5test.exe", "5text0", "5text1"], + filesRemoved: true + }, { + relPathDir: DIR_RESOURCES + "6/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "7/", + dirRemoved: true, + files: ["7text0", "7text1"], + subDirs: ["70/", "71/"], + subDirFiles: ["7xtest.exe", "7xtext0", "7xtext1"] + }, { + relPathDir: DIR_RESOURCES + "8/", + dirRemoved: false + }, { + relPathDir: DIR_RESOURCES + "8/80/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "8/81/", + dirRemoved: false, + files: ["81text0", "81text1"] + }, { + relPathDir: DIR_RESOURCES + "8/82/", + dirRemoved: false, + subDirs: ["820/", "821/"] + }, { + relPathDir: DIR_RESOURCES + "8/83/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "8/84/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "8/85/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "8/86/", + dirRemoved: true, + files: ["86text0", "86text1"] + }, { + relPathDir: DIR_RESOURCES + "8/87/", + dirRemoved: true, + subDirs: ["870/", "871/"], + subDirFiles: ["87xtext0", "87xtext1"] + }, { + relPathDir: DIR_RESOURCES + "8/88/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "8/89/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "9/90/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "9/91/", + dirRemoved: false, + files: ["91text0", "91text1"] + }, { + relPathDir: DIR_RESOURCES + "9/92/", + dirRemoved: false, + subDirs: ["920/", "921/"] + }, { + relPathDir: DIR_RESOURCES + "9/93/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "9/94/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "9/95/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "9/96/", + dirRemoved: true, + files: ["96text0", "96text1"] + }, { + relPathDir: DIR_RESOURCES + "9/97/", + dirRemoved: true, + subDirs: ["970/", "971/"], + subDirFiles: ["97xtext0", "97xtext1"] + }, { + relPathDir: DIR_RESOURCES + "9/98/", + dirRemoved: true + }, { + relPathDir: DIR_RESOURCES + "9/99/", + dirRemoved: true + }]; + +// Directories for a complete successful update. This array can be used for a +// complete failed update by calling setTestFilesAndDirsForFailure. +var gTestDirsCompleteSuccess = [ + { + description: "Removed by precomplete (rmdir)", + relPathDir: DIR_RESOURCES + "2/20/", + dirRemoved: true + }, { + description: "Removed by precomplete (rmdir)", + relPathDir: DIR_RESOURCES + "2/", + dirRemoved: true + }]; + +// Concatenate the common files to the beginning of the array. +gTestDirsCompleteSuccess = gTestDirsCommon.concat(gTestDirsCompleteSuccess); + +// Directories for a partial successful update. This array can be used for a +// partial failed update by calling setTestFilesAndDirsForFailure. +var gTestDirsPartialSuccess = [ + { + description: "Removed by update.manifest (rmdir)", + relPathDir: DIR_RESOURCES + "1/10/", + dirRemoved: true + }, { + description: "Removed by update.manifest (rmdir)", + relPathDir: DIR_RESOURCES + "1/", + dirRemoved: true + }]; + +// Concatenate the common files to the beginning of the array. +gTestDirsPartialSuccess = gTestDirsCommon.concat(gTestDirsPartialSuccess); + +// This makes it possible to run most tests on xulrunner where the update +// channel default preference is not set. +if (MOZ_APP_NAME == "xulrunner") { + try { + gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL); + } catch (e) { + setUpdateChannel("test_channel"); + } +} + +/** + * Helper function for setting up the test environment. + */ +function setupTestCommon() { + debugDump("start - general test setup"); + + Assert.strictEqual(gTestID, undefined, + "gTestID should be 'undefined' (setupTestCommon should " + + "only be called once)"); + + let caller = Components.stack.caller; + gTestID = caller.filename.toString().split("/").pop().split(".")[0]; + + // Tests that don't work with XULRunner. + const XUL_RUNNER_INCOMPATIBLE = ["marAppApplyUpdateAppBinInUseStageSuccess_win", + "marAppApplyUpdateStageSuccess", + "marAppApplyUpdateSuccess", + "marAppApplyUpdateAppBinInUseStageSuccessSvc_win", + "marAppApplyUpdateStageSuccessSvc", + "marAppApplyUpdateSuccessSvc"]; + // Replace with Array.prototype.includes when it has stabilized. + if (MOZ_APP_NAME == "xulrunner" && + XUL_RUNNER_INCOMPATIBLE.indexOf(gTestID) != -1) { + logTestInfo("Unable to run this test on xulrunner"); + return false; + } + + if (IS_SERVICE_TEST && !shouldRunServiceTest()) { + return false; + } + + do_test_pending(); + + setDefaultPrefs(); + + // Don't attempt to show a prompt when an update finishes. + Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true); + + gGREDirOrig = getGREDir(); + gGREBinDirOrig = getGREBinDir(); + gAppDirOrig = getAppBaseDir(); + + let applyDir = getApplyDirFile(null, true).parent; + + // Try to remove the directory used to apply updates and the updates directory + // on platforms other than Windows. Since the test hasn't ran yet and the + // directory shouldn't exist finished this is non-fatal for the test. + if (applyDir.exists()) { + debugDump("attempting to remove directory. Path: " + applyDir.path); + try { + removeDirRecursive(applyDir); + } catch (e) { + logTestInfo("non-fatal error removing directory. Path: " + + applyDir.path + ", Exception: " + e); + // When the application doesn't exit properly it can cause the test to + // fail again on the second run with an NS_ERROR_FILE_ACCESS_DENIED error + // along with no useful information in the test log. To prevent this use + // a different directory for the test when it isn't possible to remove the + // existing test directory (bug 1294196). + gTestID += "_new"; + logTestInfo("using a new directory for the test by changing gTestID " + + "since there is an existing test directory that can't be " + + "removed, gTestID: " + gTestID); + } + } + + if (IS_WIN) { + Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, + IS_SERVICE_TEST ? true : false); + } + + // adjustGeneralPaths registers a cleanup function that calls end_test when + // it is defined as a function. + adjustGeneralPaths(); + // Logged once here instead of in the mock directory provider to lessen test + // log spam. + debugDump("Updates Directory (UpdRootD) Path: " + getMockUpdRootD().path); + + // This prevents a warning about not being able to find the greprefs.js file + // from being logged. + let grePrefsFile = getGREDir(); + if (!grePrefsFile.exists()) { + grePrefsFile.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY); + } + grePrefsFile.append("greprefs.js"); + if (!grePrefsFile.exists()) { + grePrefsFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE); + } + + // Remove the updates directory on Windows and Mac OS X which is located + // outside of the application directory after the call to adjustGeneralPaths + // has set it up. Since the test hasn't ran yet and the directory shouldn't + // exist this is non-fatal for the test. + if (IS_WIN || IS_MACOSX) { + let updatesDir = getMockUpdRootD(); + if (updatesDir.exists()) { + debugDump("attempting to remove directory. Path: " + updatesDir.path); + try { + removeDirRecursive(updatesDir); + } catch (e) { + logTestInfo("non-fatal error removing directory. Path: " + + updatesDir.path + ", Exception: " + e); + } + } + } + + debugDump("finish - general test setup"); + return true; +} + +/** + * Nulls out the most commonly used global vars used by tests to prevent leaks + * as needed and attempts to restore the system to its original state. + */ +function cleanupTestCommon() { + debugDump("start - general test cleanup"); + + // Force the update manager to reload the update data to prevent it from + // writing the old data to the files that have just been removed. + reloadUpdateManagerData(); + + if (gChannel) { + gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer); + } + + // Call app update's observe method passing xpcom-shutdown to test that the + // shutdown of app update runs without throwing or leaking. The observer + // method is used directly instead of calling notifyObservers so components + // outside of the scope of this test don't assert and thereby cause app update + // tests to fail. + gAUS.observe(null, "xpcom-shutdown", ""); + + gTestserver = null; + + if (IS_UNIX) { + // This will delete the launch script if it exists. + getLaunchScript(); + } + + if (IS_WIN && MOZ_APP_BASENAME) { + let appDir = getApplyDirFile(null, true); + let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla"; + const REG_PATH = "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME + + "\\TaskBarIDs"; + let key = Cc["@mozilla.org/windows-registry-key;1"]. + createInstance(Ci.nsIWindowsRegKey); + try { + key.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH, + Ci.nsIWindowsRegKey.ACCESS_ALL); + if (key.hasValue(appDir.path)) { + key.removeValue(appDir.path); + } + } catch (e) { + } + try { + key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, REG_PATH, + Ci.nsIWindowsRegKey.ACCESS_ALL); + if (key.hasValue(appDir.path)) { + key.removeValue(appDir.path); + } + } catch (e) { + } + } + + // The updates directory is located outside of the application directory and + // needs to be removed on Windows and Mac OS X. + if (IS_WIN || IS_MACOSX) { + let updatesDir = getMockUpdRootD(); + // Try to remove the directory used to apply updates. Since the test has + // already finished this is non-fatal for the test. + if (updatesDir.exists()) { + debugDump("attempting to remove directory. Path: " + updatesDir.path); + try { + removeDirRecursive(updatesDir); + } catch (e) { + logTestInfo("non-fatal error removing directory. Path: " + + updatesDir.path + ", Exception: " + e); + } + if (IS_MACOSX) { + let updatesRootDir = gUpdatesRootDir.clone(); + while (updatesRootDir.path != updatesDir.path) { + if (updatesDir.exists()) { + debugDump("attempting to remove directory. Path: " + + updatesDir.path); + try { + // Try to remove the directory without the recursive flag set + // since the top level directory has already had its contents + // removed and the parent directory might still be used by a + // different test. + updatesDir.remove(false); + } catch (e) { + logTestInfo("non-fatal error removing directory. Path: " + + updatesDir.path + ", Exception: " + e); + if (e == Cr.NS_ERROR_FILE_DIR_NOT_EMPTY) { + break; + } + } + } + updatesDir = updatesDir.parent; + } + } + } + } + + let applyDir = getApplyDirFile(null, true).parent; + + // Try to remove the directory used to apply updates. Since the test has + // already finished this is non-fatal for the test. + if (applyDir.exists()) { + debugDump("attempting to remove directory. Path: " + applyDir.path); + try { + removeDirRecursive(applyDir); + } catch (e) { + logTestInfo("non-fatal error removing directory. Path: " + + applyDir.path + ", Exception: " + e); + } + } + + resetEnvironment(); + + debugDump("finish - general test cleanup"); +} + +/** + * Helper function that calls do_test_finished that tracks whether a parallel + * run of a test passed when it runs synchronously so the log output can be + * inspected. + */ +function doTestFinish() { + if (DEBUG_AUS_TEST) { + // This prevents do_print errors from being printed by the xpcshell test + // harness due to nsUpdateService.js logging to the console when the + // app.update.log preference is true. + Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false); + gAUS.observe(null, "nsPref:changed", PREF_APP_UPDATE_LOG); + } + do_execute_soon(do_test_finished); +} + +/** + * Sets the most commonly used preferences used by tests + */ +function setDefaultPrefs() { + Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, true); + if (DEBUG_AUS_TEST) { + // Enable Update logging + Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, true); + } else { + // Some apps set this preference to true by default + Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false); + } + // In case telemetry is enabled for xpcshell tests. + Services.prefs.setBoolPref(PREF_TOOLKIT_TELEMETRY_ENABLED, false); +} + +/** + * Helper function for updater binary tests that sets the appropriate values + * to check for update failures. + */ +function setTestFilesAndDirsForFailure() { + gTestFiles.forEach(function STFADFF_Files(aTestFile) { + aTestFile.compareContents = aTestFile.originalContents; + aTestFile.compareFile = aTestFile.originalFile; + aTestFile.comparePerms = aTestFile.originalPerms; + }); + + gTestDirs.forEach(function STFADFF_Dirs(aTestDir) { + aTestDir.dirRemoved = false; + if (aTestDir.filesRemoved) { + aTestDir.filesRemoved = false; + } + }); +} + +/** + * Helper function for updater binary tests that prevents the distribution + * directory files from being created. + */ +function preventDistributionFiles() { + gTestFiles = gTestFiles.filter(function(aTestFile) { + return aTestFile.relPathDir.indexOf("distribution/") == -1; + }); + + gTestDirs = gTestDirs.filter(function(aTestDir) { + return aTestDir.relPathDir.indexOf("distribution/") == -1; + }); +} + +/** + * On Mac OS X this sets the last modified time for the app bundle directory to + * a date in the past to test that the last modified time is updated when an + * update has been successfully applied (bug 600098). + */ +function setAppBundleModTime() { + if (!IS_MACOSX) { + return; + } + let now = Date.now(); + let yesterday = now - (1000 * 60 * 60 * 24); + let applyToDir = getApplyDirFile(); + applyToDir.lastModifiedTime = yesterday; +} + +/** + * On Mac OS X this checks that the last modified time for the app bundle + * directory has been updated when an update has been successfully applied + * (bug 600098). + */ +function checkAppBundleModTime() { + if (!IS_MACOSX) { + return; + } + let now = Date.now(); + let applyToDir = getApplyDirFile(); + let timeDiff = Math.abs(applyToDir.lastModifiedTime - now); + Assert.ok(timeDiff < MAC_MAX_TIME_DIFFERENCE, + "the last modified time on the apply to directory should " + + "change after a successful update"); +} + +/** + * On Mac OS X and Windows this checks if the post update '.running' file exists + * to determine if the post update binary was launched. + * + * @param aShouldExist + * Whether the post update '.running' file should exist. + */ +function checkPostUpdateRunningFile(aShouldExist) { + if (!IS_WIN && !IS_MACOSX) { + return; + } + let postUpdateRunningFile = getPostUpdateFile(".running"); + if (aShouldExist) { + Assert.ok(postUpdateRunningFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(postUpdateRunningFile.path)); + } else { + Assert.ok(!postUpdateRunningFile.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(postUpdateRunningFile.path)); + } +} + +/** + * Initializes the most commonly used settings and creates an instance of the + * update service stub. + */ +function standardInit() { + createAppInfo("xpcshell@tests.mozilla.org", APP_INFO_NAME, "1.0", "2.0"); + // Initialize the update service stub component + initUpdateServiceStub(); +} + +/** + * Helper function for getting the application version from the application.ini + * file. This will look in both the GRE and the application directories for the + * application.ini file. + * + * @return The version string from the application.ini file. + */ +function getAppVersion() { + // Read the application.ini and use its application version. + let iniFile = gGREDirOrig.clone(); + iniFile.append(FILE_APPLICATION_INI); + if (!iniFile.exists()) { + iniFile = gGREBinDirOrig.clone(); + iniFile.append(FILE_APPLICATION_INI); + } + Assert.ok(iniFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(iniFile.path)); + let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. + getService(Ci.nsIINIParserFactory). + createINIParser(iniFile); + return iniParser.getString("App", "Version"); +} + +/** + * Helper function for getting the relative path to the directory where the + * application binary is located (e.g. /dir.app/). + * + * Note: The dir.app subdirectory under is needed for + * platforms other than Mac OS X so the tests can run in parallel due to + * update staging creating a lock file named moz_update_in_progress.lock in + * the parent directory of the installation directory. + * + * @return The relative path to the directory where application binary is + * located. + */ +function getApplyDirPath() { + return gTestID + "/dir.app/"; +} + +/** + * Helper function for getting the nsIFile for a file in the directory where the + * update will be applied. + * + * The files for the update are located two directories below the apply to + * directory since Mac OS X sets the last modified time for the root directory + * to the current time and if the update changes any files in the root directory + * then it wouldn't be possible to test (bug 600098). + * + * @param aRelPath (optional) + * The relative path to the file or directory to get from the root of + * the test's directory. If not specified the test's directory will be + * returned. + * @param aAllowNonexistent (optional) + * Whether the file must exist. If false or not specified the file must + * exist or the function will throw. + * @return The nsIFile for the file in the directory where the update will be + * applied. + * @throws If aAllowNonexistent is not specified or is false and the file or + * directory does not exist. + */ +function getApplyDirFile(aRelPath, aAllowNonexistent) { + let relpath = getApplyDirPath() + (aRelPath ? aRelPath : ""); + return do_get_file(relpath, aAllowNonexistent); +} + +/** + * Helper function for getting the nsIFile for a file in the directory where the + * update will be staged. + * + * The files for the update are located two directories below the stage + * directory since Mac OS X sets the last modified time for the root directory + * to the current time and if the update changes any files in the root directory + * then it wouldn't be possible to test (bug 600098). + * + * @param aRelPath (optional) + * The relative path to the file or directory to get from the root of + * the stage directory. If not specified the stage directory will be + * returned. + * @param aAllowNonexistent (optional) + * Whether the file must exist. If false or not specified the file must + * exist or the function will throw. + * @return The nsIFile for the file in the directory where the update will be + * staged. + * @throws If aAllowNonexistent is not specified or is false and the file or + * directory does not exist. + */ +function getStageDirFile(aRelPath, aAllowNonexistent) { + if (IS_MACOSX) { + let file = getMockUpdRootD(); + file.append(DIR_UPDATES); + file.append(DIR_PATCH); + file.append(DIR_UPDATED); + if (aRelPath) { + let pathParts = aRelPath.split("/"); + for (let i = 0; i < pathParts.length; i++) { + if (pathParts[i]) { + file.append(pathParts[i]); + } + } + } + if (!aAllowNonexistent) { + Assert.ok(file.exists(), + MSG_SHOULD_EXIST + getMsgPath(file.path)); + } + return file; + } + + let relpath = getApplyDirPath() + DIR_UPDATED + "/" + (aRelPath ? aRelPath : ""); + return do_get_file(relpath, aAllowNonexistent); +} + +/** + * Helper function for getting the relative path to the directory where the + * test data files are located. + * + * @return The relative path to the directory where the test data files are + * located. + */ +function getTestDirPath() { + return "../data/"; +} + +/** + * Helper function for getting the nsIFile for a file in the test data + * directory. + * + * @param aRelPath (optional) + * The relative path to the file or directory to get from the root of + * the test's data directory. If not specified the test's data + * directory will be returned. + * @param aAllowNonExists (optional) + * Whether or not to throw an error if the path exists. + * If not specified, then false is used. + * @return The nsIFile for the file in the test data directory. + * @throws If the file or directory does not exist. + */ +function getTestDirFile(aRelPath, aAllowNonExists) { + let relpath = getTestDirPath() + (aRelPath ? aRelPath : ""); + return do_get_file(relpath, !!aAllowNonExists); +} + +/** + * Helper function for getting the nsIFile for the maintenance service + * directory on Windows. + * + * @return The nsIFile for the maintenance service directory. + */ +function getMaintSvcDir() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + const CSIDL_PROGRAM_FILES = 0x26; + const CSIDL_PROGRAM_FILESX86 = 0x2A; + // This will return an empty string on our Win XP build systems. + let maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILESX86); + if (maintSvcDir) { + maintSvcDir.append("Mozilla Maintenance Service"); + debugDump("using CSIDL_PROGRAM_FILESX86 - maintenance service install " + + "directory path: " + maintSvcDir.path); + } + if (!maintSvcDir || !maintSvcDir.exists()) { + maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILES); + if (maintSvcDir) { + maintSvcDir.append("Mozilla Maintenance Service"); + debugDump("using CSIDL_PROGRAM_FILES - maintenance service install " + + "directory path: " + maintSvcDir.path); + } + } + if (!maintSvcDir) { + do_throw("Unable to find the maintenance service install directory"); + } + + return maintSvcDir; +} + +/** + * Get the nsILocalFile for a Windows special folder determined by the CSIDL + * passed. + * + * @param aCSIDL + * The CSIDL for the Windows special folder. + * @return The nsILocalFile for the Windows special folder. + * @throws If called from a platform other than Windows. + */ +function getSpecialFolderDir(aCSIDL) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let lib = ctypes.open("shell32"); + let SHGetSpecialFolderPath = lib.declare("SHGetSpecialFolderPathW", + ctypes.winapi_abi, + ctypes.bool, /* bool(return) */ + ctypes.int32_t, /* HWND hwndOwner */ + ctypes.char16_t.ptr, /* LPTSTR lpszPath */ + ctypes.int32_t, /* int csidl */ + ctypes.bool /* BOOL fCreate */); + + let aryPath = ctypes.char16_t.array()(260); + let rv = SHGetSpecialFolderPath(0, aryPath, aCSIDL, false); + lib.close(); + + let path = aryPath.readString(); // Convert the c-string to js-string + if (!path) { + return null; + } + debugDump("SHGetSpecialFolderPath returned path: " + path); + let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + dir.initWithPath(path); + return dir; +} + +XPCOMUtils.defineLazyGetter(this, "gInstallDirPathHash", function test_gIDPH() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + // Figure out where we should check for a cached hash value + if (!MOZ_APP_BASENAME) { + return null; + } + + let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla"; + let appDir = getApplyDirFile(null, true); + + const REG_PATH = "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME + + "\\TaskBarIDs"; + let regKey = Cc["@mozilla.org/windows-registry-key;1"]. + createInstance(Ci.nsIWindowsRegKey); + try { + regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH, + Ci.nsIWindowsRegKey.ACCESS_ALL); + regKey.writeStringValue(appDir.path, gTestID); + return gTestID; + } catch (e) { + } + + try { + regKey.create(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, REG_PATH, + Ci.nsIWindowsRegKey.ACCESS_ALL); + regKey.writeStringValue(appDir.path, gTestID); + return gTestID; + } catch (e) { + logTestInfo("failed to create registry key. Registry Path: " + REG_PATH + + ", Key Name: " + appDir.path + ", Key Value: " + gTestID + + ", Exception " + e); + } + return null; +}); + +XPCOMUtils.defineLazyGetter(this, "gLocalAppDataDir", function test_gLADD() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + const CSIDL_LOCAL_APPDATA = 0x1c; + return getSpecialFolderDir(CSIDL_LOCAL_APPDATA); +}); + +XPCOMUtils.defineLazyGetter(this, "gProgFilesDir", function test_gPFD() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + const CSIDL_PROGRAM_FILES = 0x26; + return getSpecialFolderDir(CSIDL_PROGRAM_FILES); +}); + +/** + * Helper function for getting the update root directory used by the tests. This + * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir + * in nsXREDirProvider.cpp so an application will be able to find the update + * when running a test that launches the application. + */ +function getMockUpdRootD() { + if (IS_WIN) { + return getMockUpdRootDWin(); + } + + if (IS_MACOSX) { + return getMockUpdRootDMac(); + } + + return getApplyDirFile(DIR_MACOS, true); +} + +/** + * Helper function for getting the update root directory used by the tests. This + * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir + * in nsXREDirProvider.cpp so an application will be able to find the update + * when running a test that launches the application. + */ +function getMockUpdRootDWin() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let localAppDataDir = gLocalAppDataDir.clone(); + let progFilesDir = gProgFilesDir.clone(); + let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent; + + let appDirPath = appDir.path; + let relPathUpdates = ""; + if (gInstallDirPathHash && (MOZ_APP_VENDOR || MOZ_APP_BASENAME)) { + relPathUpdates += (MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME) + + "\\" + DIR_UPDATES + "\\" + gInstallDirPathHash; + } + + if (!relPathUpdates && progFilesDir) { + if (appDirPath.length > progFilesDir.path.length) { + if (appDirPath.substr(0, progFilesDir.path.length) == progFilesDir.path) { + if (MOZ_APP_VENDOR && MOZ_APP_BASENAME) { + relPathUpdates += MOZ_APP_VENDOR + "\\" + MOZ_APP_BASENAME; + } else { + relPathUpdates += MOZ_APP_BASENAME; + } + relPathUpdates += appDirPath.substr(progFilesDir.path.length); + } + } + } + + if (!relPathUpdates) { + if (MOZ_APP_VENDOR && MOZ_APP_BASENAME) { + relPathUpdates += MOZ_APP_VENDOR + "\\" + MOZ_APP_BASENAME; + } else { + relPathUpdates += MOZ_APP_BASENAME; + } + relPathUpdates += "\\" + MOZ_APP_NAME; + } + + let updatesDir = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsILocalFile); + updatesDir.initWithPath(localAppDataDir.path + "\\" + relPathUpdates); + return updatesDir; +} + +XPCOMUtils.defineLazyGetter(this, "gUpdatesRootDir", function test_gURD() { + if (!IS_MACOSX) { + do_throw("Mac OS X only function called by a different platform!"); + } + + let dir = Services.dirsvc.get("ULibDir", Ci.nsILocalFile); + dir.append("Caches"); + if (MOZ_APP_VENDOR || MOZ_APP_BASENAME) { + dir.append(MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME); + } else { + dir.append("Mozilla"); + } + dir.append(DIR_UPDATES); + return dir; +}); + +/** + * Helper function for getting the update root directory used by the tests. This + * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir + * in nsXREDirProvider.cpp so an application will be able to find the update + * when running a test that launches the application. + */ +function getMockUpdRootDMac() { + if (!IS_MACOSX) { + do_throw("Mac OS X only function called by a different platform!"); + } + + let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile). + parent.parent.parent; + let appDirPath = appDir.path; + appDirPath = appDirPath.substr(0, appDirPath.length - 4); + + let pathUpdates = gUpdatesRootDir.path + appDirPath; + let updatesDir = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsILocalFile); + updatesDir.initWithPath(pathUpdates); + return updatesDir; +} + +/** + * Creates an update in progress lock file in the specified directory on + * Windows. + * + * @param aDir + * The nsIFile for the directory where the lock file should be created. + */ +function createUpdateInProgressLockFile(aDir) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let file = aDir.clone(); + file.append(FILE_UPDATE_IN_PROGRESS_LOCK); + file.create(file.NORMAL_FILE_TYPE, 0o444); + file.QueryInterface(Ci.nsILocalFileWin); + file.fileAttributesWin |= file.WFA_READONLY; + file.fileAttributesWin &= ~file.WFA_READWRITE; + Assert.ok(file.exists(), + MSG_SHOULD_EXIST + getMsgPath(file.path)); + Assert.ok(!file.isWritable(), + "the lock file should not be writeable"); +} + +/** + * Removes an update in progress lock file in the specified directory on + * Windows. + * + * @param aDir + * The nsIFile for the directory where the lock file is located. + */ +function removeUpdateInProgressLockFile(aDir) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let file = aDir.clone(); + file.append(FILE_UPDATE_IN_PROGRESS_LOCK); + file.QueryInterface(Ci.nsILocalFileWin); + file.fileAttributesWin |= file.WFA_READWRITE; + file.fileAttributesWin &= ~file.WFA_READONLY; + file.remove(false); + Assert.ok(!file.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(file.path)); +} + +/** + * Gets the test updater from the test data direcory. + * + * @return nsIFIle for the test updater. + */ +function getTestUpdater() { + let updater = getTestDirFile("updater.app", true); + if (!updater.exists()) { + updater = getTestDirFile(FILE_UPDATER_BIN); + if (!updater.exists()) { + do_throw("Unable to find the updater binary!"); + } + } + Assert.ok(updater.exists(), + MSG_SHOULD_EXIST + getMsgPath(updater.path)); + return updater; +} + +/** + * Copies the test updater to the GRE binary directory and returns the nsIFile + * for the copied test updater. + * + * @return nsIFIle for the copied test updater. + */ +function copyTestUpdaterToBinDir() { + let testUpdater = getTestUpdater(); + let updater = getGREBinDir(); + updater.append(testUpdater.leafName); + if (!updater.exists()) { + testUpdater.copyToFollowingLinks(updater.parent, updater.leafName); + } + return updater; +} + +/** + * Copies the test updater to the location where it will be launched to apply an + * update and returns the nsIFile for the copied test updater. + * + * @return nsIFIle for the copied test updater. + */ +function copyTestUpdaterForRunUsingUpdater() { + if (IS_WIN) { + return copyTestUpdaterToBinDir(); + } + + let testUpdater = getTestUpdater(); + let updater = getUpdatesPatchDir(); + updater.append(testUpdater.leafName); + if (!updater.exists()) { + testUpdater.copyToFollowingLinks(updater.parent, updater.leafName); + } + + if (IS_MACOSX) { + updater.append("Contents"); + updater.append("MacOS"); + updater.append("org.mozilla.updater"); + } + return updater; +} + +/** + * Logs the contents of an update log and for maintenance service tests this + * will log the contents of the latest maintenanceservice.log. + * + * @param aLogLeafName + * The leaf name of the update log. + */ +function logUpdateLog(aLogLeafName) { + let updateLog = getUpdateLog(aLogLeafName); + if (updateLog.exists()) { + // xpcshell tests won't display the entire contents so log each line. + let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n"); + updateLogContents = replaceLogPaths(updateLogContents); + let aryLogContents = updateLogContents.split("\n"); + logTestInfo("contents of " + updateLog.path + ":"); + aryLogContents.forEach(function RU_LC_FE(aLine) { + logTestInfo(aLine); + }); + } else { + logTestInfo("update log doesn't exist, path: " + updateLog.path); + } + + if (IS_SERVICE_TEST) { + let serviceLog = getMaintSvcDir(); + serviceLog.append("logs"); + serviceLog.append("maintenanceservice.log"); + if (serviceLog.exists()) { + // xpcshell tests won't display the entire contents so log each line. + let serviceLogContents = readFileBytes(serviceLog).replace(/\r\n/g, "\n"); + serviceLogContents = replaceLogPaths(serviceLogContents); + let aryLogContents = serviceLogContents.split("\n"); + logTestInfo("contents of " + serviceLog.path + ":"); + aryLogContents.forEach(function RU_LC_FE(aLine) { + logTestInfo(aLine); + }); + } else { + logTestInfo("maintenance service log doesn't exist, path: " + + serviceLog.path); + } + } +} + +/** + * Gets the maintenance service log contents. + */ +function readServiceLogFile() { + let file = getMaintSvcDir(); + file.append("logs"); + file.append("maintenanceservice.log"); + return readFile(file); +} + +/** + * Launches the updater binary to apply an update for updater tests. + * + * @param aExpectedStatus + * The expected value of update.status when the test finishes. For + * service tests passing STATE_PENDING or STATE_APPLIED will change the + * value to STATE_PENDING_SVC and STATE_APPLIED_SVC respectively. + * @param aSwitchApp + * If true the update should switch the application with an updated + * staged application and if false the update should be applied to the + * installed application. + * @param aExpectedExitValue + * The expected exit value from the updater binary for non-service + * tests. + * @param aCheckSvcLog + * Whether the service log should be checked for service tests. + * @param aPatchDirPath (optional) + * When specified the patch directory path to use for invalid argument + * tests otherwise the normal path will be used. + * @param aInstallDirPath (optional) + * When specified the install directory path to use for invalid + * argument tests otherwise the normal path will be used. + * @param aApplyToDirPath (optional) + * When specified the apply to / working directory path to use for + * invalid argument tests otherwise the normal path will be used. + * @param aCallbackPath (optional) + * When specified the callback path to use for invalid argument tests + * otherwise the normal path will be used. + */ +function runUpdate(aExpectedStatus, aSwitchApp, aExpectedExitValue, aCheckSvcLog, + aPatchDirPath, aInstallDirPath, aApplyToDirPath, + aCallbackPath) { + let isInvalidArgTest = !!aPatchDirPath || !!aInstallDirPath || + !!aApplyToDirPath || aCallbackPath; + + let svcOriginalLog; + if (IS_SERVICE_TEST) { + copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_BIN, false); + copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_INSTALLER_BIN, false); + if (aCheckSvcLog) { + svcOriginalLog = readServiceLogFile(); + } + } + + // Copy the updater binary to the directory where it will apply updates. + let updateBin = copyTestUpdaterForRunUsingUpdater(); + Assert.ok(updateBin.exists(), + MSG_SHOULD_EXIST + getMsgPath(updateBin.path)); + + let updatesDirPath = aPatchDirPath || getUpdatesPatchDir().path; + let installDirPath = aInstallDirPath || getApplyDirFile(null, true).path; + let applyToDirPath = aApplyToDirPath || getApplyDirFile(null, true).path; + let stageDirPath = aApplyToDirPath || getStageDirFile(null, true).path; + + let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile); + callbackApp.permissions = PERMS_DIRECTORY; + + setAppBundleModTime(); + + let args = [updatesDirPath, installDirPath]; + if (aSwitchApp) { + args[2] = stageDirPath; + args[3] = "0/replace"; + } else { + args[2] = applyToDirPath; + args[3] = "0"; + } + + let launchBin = IS_SERVICE_TEST && isInvalidArgTest ? callbackApp : updateBin; + + if (!isInvalidArgTest) { + args = args.concat([callbackApp.parent.path, callbackApp.path]); + args = args.concat(gCallbackArgs); + } else if (IS_SERVICE_TEST) { + args = ["launch-service", updateBin.path].concat(args); + } else if (aCallbackPath) { + args = args.concat([callbackApp.parent.path, aCallbackPath]); + } + + debugDump("launching the program: " + launchBin.path + " " + args.join(" ")); + + if (aSwitchApp && !isInvalidArgTest) { + // We want to set the env vars again + gShouldResetEnv = undefined; + } + + setEnvironment(); + + let process = Cc["@mozilla.org/process/util;1"]. + createInstance(Ci.nsIProcess); + process.init(launchBin); + process.run(true, args, args.length); + + resetEnvironment(); + + let status = readStatusFile(); + if ((!IS_SERVICE_TEST && process.exitValue != aExpectedExitValue) || + status != aExpectedStatus) { + if (process.exitValue != aExpectedExitValue) { + logTestInfo("updater exited with unexpected value! Got: " + + process.exitValue + ", Expected: " + aExpectedExitValue); + } + if (status != aExpectedStatus) { + logTestInfo("update status is not the expected status! Got: " + status + + ", Expected: " + aExpectedStatus); + } + logUpdateLog(FILE_LAST_UPDATE_LOG); + } + + if (!IS_SERVICE_TEST) { + Assert.equal(process.exitValue, aExpectedExitValue, + "the process exit value" + MSG_SHOULD_EQUAL); + } + Assert.equal(status, aExpectedStatus, + "the update status" + MSG_SHOULD_EQUAL); + + if (IS_SERVICE_TEST && aCheckSvcLog) { + let contents = readServiceLogFile(); + Assert.notEqual(contents, svcOriginalLog, + "the contents of the maintenanceservice.log should not " + + "be the same as the original contents"); + if (!isInvalidArgTest) { + Assert.notEqual(contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH), -1, + "the contents of the maintenanceservice.log should " + + "contain the successful launch string"); + } + } + + do_execute_soon(runUpdateFinished); +} + +/** + * Launches the helper binary synchronously with the specified arguments for + * updater tests. + * + * @param aArgs + * The arguments to pass to the helper binary. + * @return the process exit value returned by the helper binary. + */ +function runTestHelperSync(aArgs) { + let helperBin = getTestDirFile(FILE_HELPER_BIN); + let process = Cc["@mozilla.org/process/util;1"]. + createInstance(Ci.nsIProcess); + process.init(helperBin); + debugDump("Running " + helperBin.path + " " + aArgs.join(" ")); + process.run(true, aArgs, aArgs.length); + return process.exitValue; +} + +/** + * Creates a symlink for updater tests. + */ +function createSymlink() { + let args = ["setup-symlink", "moz-foo", "moz-bar", "target", + getApplyDirFile().path + "/" + DIR_RESOURCES + "link"]; + let exitValue = runTestHelperSync(args); + Assert.equal(exitValue, 0, + "the helper process exit value should be 0"); + getApplyDirFile(DIR_RESOURCES + "link", false).permissions = 0o666; + args = ["setup-symlink", "moz-foo2", "moz-bar2", "target2", + getApplyDirFile().path + "/" + DIR_RESOURCES + "link2", "change-perm"]; + exitValue = runTestHelperSync(args); + Assert.equal(exitValue, 0, + "the helper process exit value should be 0"); +} + +/** + * Removes a symlink for updater tests. + */ +function removeSymlink() { + let args = ["remove-symlink", "moz-foo", "moz-bar", "target", + getApplyDirFile().path + "/" + DIR_RESOURCES + "link"]; + let exitValue = runTestHelperSync(args); + Assert.equal(exitValue, 0, + "the helper process exit value should be 0"); + args = ["remove-symlink", "moz-foo2", "moz-bar2", "target2", + getApplyDirFile().path + "/" + DIR_RESOURCES + "link2"]; + exitValue = runTestHelperSync(args); + Assert.equal(exitValue, 0, + "the helper process exit value should be 0"); +} + +/** + * Checks a symlink for updater tests. + */ +function checkSymlink() { + let args = ["check-symlink", + getApplyDirFile().path + "/" + DIR_RESOURCES + "link"]; + let exitValue = runTestHelperSync(args); + Assert.equal(exitValue, 0, + "the helper process exit value should be 0"); +} + +/** + * Sets the active update and related information for updater tests. + */ +function setupActiveUpdate() { + let state = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL); + let patches = getLocalPatchString(null, null, null, null, null, "true", + state); + let updates = getLocalUpdateString(patches, null, null, null, null, null, + null, null, null, null, "true", channel); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeVersionFile(DEFAULT_UPDATE_VERSION); + writeStatusFile(state); + reloadUpdateManagerData(); + Assert.ok(!!gUpdateManager.activeUpdate, + "the active update should be defined"); +} + +/** + * Gets the specified update log. + * + * @param aLogLeafName + * The leaf name of the log to get. + * @return nsIFile for the update log. + */ +function getUpdateLog(aLogLeafName) { + let updateLog = getUpdatesDir(); + if (aLogLeafName == FILE_UPDATE_LOG) { + updateLog.append(DIR_PATCH); + } + updateLog.append(aLogLeafName); + return updateLog; +} + +/** + * The update-staged observer for the call to nsIUpdateProcessor:processUpdate. + */ +const gUpdateStagedObserver = { + observe: function(aSubject, aTopic, aData) { + debugDump("observe called with topic: " + aTopic + ", data: " + aData); + if (aTopic == "update-staged") { + Services.obs.removeObserver(gUpdateStagedObserver, "update-staged"); + // The environment is reset after the update-staged observer topic because + // processUpdate in nsIUpdateProcessor uses a new thread and clearing the + // environment immediately after calling processUpdate can clear the + // environment before the updater is launched. + resetEnvironment(); + // Use do_execute_soon to prevent any failures from propagating to the + // update service. + do_execute_soon(checkUpdateStagedState.bind(null, aData)); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) +}; + +/** + * Stages an update using nsIUpdateProcessor:processUpdate for updater tests. + * + * @param aCheckSvcLog + * Whether the service log should be checked for service tests. + */ +function stageUpdate(aCheckSvcLog) { + debugDump("start - attempting to stage update"); + + if (IS_SERVICE_TEST && aCheckSvcLog) { + gSvcOriginalLogContents = readServiceLogFile(); + } + + Services.obs.addObserver(gUpdateStagedObserver, "update-staged", false); + + setAppBundleModTime(); + setEnvironment(); + // Stage the update. + Cc["@mozilla.org/updates/update-processor;1"]. + createInstance(Ci.nsIUpdateProcessor). + processUpdate(gUpdateManager.activeUpdate); + + // The environment is not reset here because processUpdate in + // nsIUpdateProcessor uses a new thread and clearing the environment + // immediately after calling processUpdate can clear the environment before + // the updater is launched. Instead it is reset after the update-staged + // observer topic. + + debugDump("finish - attempting to stage update"); +} + +/** + * Checks that the update state is correct as well as the expected files are + * present after staging and update for updater tests and then calls + * stageUpdateFinished. + * + * @param aUpdateState + * The update state received by the observer notification. + */ +function checkUpdateStagedState(aUpdateState) { + if (IS_WIN) { + if (IS_SERVICE_TEST) { + waitForServiceStop(false); + } else { + let updater = getApplyDirFile(FILE_UPDATER_BIN, true); + if (isFileInUse(updater)) { + do_timeout(FILE_IN_USE_TIMEOUT_MS, + checkUpdateStagedState.bind(null, aUpdateState)); + return; + } + } + } + + Assert.equal(aUpdateState, STATE_AFTER_STAGE, + "the notified state" + MSG_SHOULD_EQUAL); + + if (!gStagingRemovedUpdate) { + Assert.equal(readStatusState(), STATE_AFTER_STAGE, + "the status file state" + MSG_SHOULD_EQUAL); + + Assert.equal(gUpdateManager.activeUpdate.state, STATE_AFTER_STAGE, + "the update state" + MSG_SHOULD_EQUAL); + } + + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_STAGE, + "the update state" + MSG_SHOULD_EQUAL); + + let log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + let stageDir = getStageDirFile(null, true); + if (STATE_AFTER_STAGE == STATE_APPLIED || + STATE_AFTER_STAGE == STATE_APPLIED_SVC) { + Assert.ok(stageDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(stageDir.path)); + } else { + Assert.ok(!stageDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path)); + } + + if (IS_SERVICE_TEST && gSvcOriginalLogContents !== undefined) { + let contents = readServiceLogFile(); + Assert.notEqual(contents, gSvcOriginalLogContents, + "the contents of the maintenanceservice.log should not " + + "be the same as the original contents"); + Assert.notEqual(contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH), -1, + "the contents of the maintenanceservice.log should " + + "contain the successful launch string"); + } + + do_execute_soon(stageUpdateFinished); +} + +/** + * Helper function to check whether the maintenance service updater tests should + * run. See bug 711660 for more details. + * + * @return true if the test should run and false if it shouldn't. + */ +function shouldRunServiceTest() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let binDir = getGREBinDir(); + let updaterBin = binDir.clone(); + updaterBin.append(FILE_UPDATER_BIN); + Assert.ok(updaterBin.exists(), + MSG_SHOULD_EXIST + ", leafName: " + updaterBin.leafName); + + let updaterBinPath = updaterBin.path; + if (/ /.test(updaterBinPath)) { + updaterBinPath = '"' + updaterBinPath + '"'; + } + + let isBinSigned = isBinarySigned(updaterBinPath); + + const REG_PATH = "SOFTWARE\\Mozilla\\MaintenanceService\\" + + "3932ecacee736d366d6436db0f55bce4"; + let key = Cc["@mozilla.org/windows-registry-key;1"]. + createInstance(Ci.nsIWindowsRegKey); + try { + key.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH, + Ci.nsIWindowsRegKey.ACCESS_READ | key.WOW64_64); + } catch (e) { + // The build system could sign the files and not have the test registry key + // in which case we should fail the test if the updater binary is signed so + // the build system can be fixed by adding the registry key. + if (IS_AUTHENTICODE_CHECK_ENABLED) { + Assert.ok(!isBinSigned, + "the updater.exe binary should not be signed when the test " + + "registry key doesn't exist (if it is, build system " + + "configuration bug?)"); + } + + logTestInfo("this test can only run on the buildbot build system at this " + + "time"); + return false; + } + + // Check to make sure the service is installed + let args = ["wait-for-service-stop", "MozillaMaintenance", "10"]; + let exitValue = runTestHelperSync(args); + Assert.notEqual(exitValue, 0xEE, "the maintenance service should be " + + "installed (if not, build system configuration bug?)"); + + if (IS_AUTHENTICODE_CHECK_ENABLED) { + // The test registry key exists and IS_AUTHENTICODE_CHECK_ENABLED is true + // so the binaries should be signed. To run the test locally + // DISABLE_UPDATER_AUTHENTICODE_CHECK can be defined. + Assert.ok(isBinSigned, + "the updater.exe binary should be signed (if not, build system " + + "configuration bug?)"); + } + + // In case the machine is running an old maintenance service or if it + // is not installed, and permissions exist to install it. Then install + // the newer bin that we have since all of the other checks passed. + return attemptServiceInstall(); +} + +/** + * Helper function to check whether the a binary is signed. + * + * @param aBinPath + * The path to the file to check if it is signed. + * @return true if the file is signed and false if it isn't. + */ +function isBinarySigned(aBinPath) { + let args = ["check-signature", aBinPath]; + let exitValue = runTestHelperSync(args); + if (exitValue != 0) { + logTestInfo("binary is not signed. " + FILE_HELPER_BIN + " returned " + + exitValue + " for file " + aBinPath); + return false; + } + return true; +} + +/** + * Helper function for asynchronously setting up the application files required + * to launch the application for the updater tests by either copying or creating + * symlinks for the files. This is needed for Windows debug builds which can + * lock a file that is being copied so that the tests can run in parallel. After + * the files have been copied the setupUpdaterTestFinished function will be + * called. + */ +function setupAppFilesAsync() { + gTimeoutRuns++; + try { + setupAppFiles(); + } catch (e) { + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + do_throw("Exceeded MAX_TIMEOUT_RUNS while trying to setup application " + + "files! Exception: " + e); + } + do_execute_soon(setupAppFilesAsync); + return; + } + + do_execute_soon(setupUpdaterTestFinished); +} + +/** + * Helper function for setting up the application files required to launch the + * application for the updater tests by either copying or creating symlinks to + * the files. + */ +function setupAppFiles() { + debugDump("start - copying or creating symlinks to application files " + + "for the test"); + + let destDir = getApplyDirFile(null, true); + if (!destDir.exists()) { + try { + destDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY); + } catch (e) { + logTestInfo("unable to create directory! Path: " + destDir.path + + ", Exception: " + e); + do_throw(e); + } + } + + // Required files for the application or the test that aren't listed in the + // dependentlibs.list file. + let appFiles = [{relPath: FILE_APP_BIN, + inGreDir: false}, + {relPath: FILE_APPLICATION_INI, + inGreDir: true}, + {relPath: "dependentlibs.list", + inGreDir: true}]; + + // On Linux the updater.png must also be copied + if (IS_UNIX && !IS_MACOSX) { + appFiles.push({relPath: "icons/updater.png", + inGreDir: true}); + } + + // Read the dependent libs file leafnames from the dependentlibs.list file + // into the array. + let deplibsFile = gGREDirOrig.clone(); + deplibsFile.append("dependentlibs.list"); + let fis = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + fis.init(deplibsFile, 0x01, 0o444, Ci.nsIFileInputStream.CLOSE_ON_EOF); + fis.QueryInterface(Ci.nsILineInputStream); + + let hasMore; + let line = {}; + do { + hasMore = fis.readLine(line); + appFiles.push({relPath: line.value, + inGreDir: false}); + } while (hasMore); + + fis.close(); + + appFiles.forEach(function CMAF_FLN_FE(aAppFile) { + copyFileToTestAppDir(aAppFile.relPath, aAppFile.inGreDir); + }); + + copyTestUpdaterToBinDir(); + + debugDump("finish - copying or creating symlinks to application files " + + "for the test"); +} + +/** + * Copies the specified files from the dist/bin directory into the test's + * application directory. + * + * @param aFileRelPath + * The relative path to the source and the destination of the file to + * copy. + * @param aInGreDir + * Whether the file is located in the GRE directory which is + * /Contents/Resources on Mac OS X and is the installation + * directory on all other platforms. If false the file must be in the + * GRE Binary directory which is /Contents/MacOS on Mac OS X + * and is the installation directory on on all other platforms. + */ +function copyFileToTestAppDir(aFileRelPath, aInGreDir) { + // gGREDirOrig and gGREBinDirOrig must always be cloned when changing its + // properties + let srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone(); + let destFile = aInGreDir ? getGREDir() : getGREBinDir(); + let fileRelPath = aFileRelPath; + let pathParts = fileRelPath.split("/"); + for (let i = 0; i < pathParts.length; i++) { + if (pathParts[i]) { + srcFile.append(pathParts[i]); + destFile.append(pathParts[i]); + } + } + + if (IS_MACOSX && !srcFile.exists()) { + debugDump("unable to copy file since it doesn't exist! Checking if " + + fileRelPath + ".app exists. Path: " + srcFile.path); + // gGREDirOrig and gGREBinDirOrig must always be cloned when changing its + // properties + srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone(); + destFile = aInGreDir ? getGREDir() : getGREBinDir(); + for (let i = 0; i < pathParts.length; i++) { + if (pathParts[i]) { + srcFile.append(pathParts[i] + (pathParts.length - 1 == i ? ".app" : "")); + destFile.append(pathParts[i] + (pathParts.length - 1 == i ? ".app" : "")); + } + } + fileRelPath = fileRelPath + ".app"; + } + Assert.ok(srcFile.exists(), + MSG_SHOULD_EXIST + ", leafName: " + srcFile.leafName); + + // Symlink libraries. Note that the XUL library on Mac OS X doesn't have a + // file extension and shouldSymlink will always be false on Windows. + let shouldSymlink = (pathParts[pathParts.length - 1] == "XUL" || + fileRelPath.substr(fileRelPath.length - 3) == ".so" || + fileRelPath.substr(fileRelPath.length - 6) == ".dylib"); + if (!shouldSymlink) { + if (!destFile.exists()) { + try { + srcFile.copyToFollowingLinks(destFile.parent, destFile.leafName); + } catch (e) { + // Just in case it is partially copied + if (destFile.exists()) { + try { + destFile.remove(true); + } catch (ex) { + logTestInfo("unable to remove file that failed to copy! Path: " + + destFile.path); + } + } + do_throw("Unable to copy file! Path: " + srcFile.path + + ", Exception: " + ex); + } + } + } else { + try { + if (destFile.exists()) { + destFile.remove(false); + } + let ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + ln.initWithPath("/bin/ln"); + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(ln); + let args = ["-s", srcFile.path, destFile.path]; + process.run(true, args, args.length); + Assert.ok(destFile.isSymlink(), + destFile.leafName + " should be a symlink"); + } catch (e) { + do_throw("Unable to create symlink for file! Path: " + srcFile.path + + ", Exception: " + e); + } + } +} + +/** + * Attempts to upgrade the maintenance service if permissions are allowed. + * This is useful for XP where we have permission to upgrade in case an + * older service installer exists. Also if the user manually installed into + * a unprivileged location. + * + * @return true if the installed service is from this build. If the installed + * service is not from this build the test will fail instead of + * returning false. + */ +function attemptServiceInstall() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let maintSvcDir = getMaintSvcDir(); + Assert.ok(maintSvcDir.exists(), + MSG_SHOULD_EXIST + ", leafName: " + maintSvcDir.leafName); + let oldMaintSvcBin = maintSvcDir.clone(); + oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN); + Assert.ok(oldMaintSvcBin.exists(), + MSG_SHOULD_EXIST + ", leafName: " + oldMaintSvcBin.leafName); + let buildMaintSvcBin = getGREBinDir(); + buildMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN); + if (readFileBytes(oldMaintSvcBin) == readFileBytes(buildMaintSvcBin)) { + debugDump("installed maintenance service binary is the same as the " + + "build's maintenance service binary"); + return true; + } + let backupMaintSvcBin = maintSvcDir.clone(); + backupMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN + ".backup"); + try { + if (backupMaintSvcBin.exists()) { + backupMaintSvcBin.remove(false); + } + oldMaintSvcBin.moveTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN + ".backup"); + buildMaintSvcBin.copyTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN); + backupMaintSvcBin.remove(false); + } catch (e) { + // Restore the original file in case the moveTo was successful. + if (backupMaintSvcBin.exists()) { + oldMaintSvcBin = maintSvcDir.clone(); + oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN); + if (!oldMaintSvcBin.exists()) { + backupMaintSvcBin.moveTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN); + } + } + Assert.ok(false, "should be able copy the test maintenance service to " + + "the maintenance service directory (if not, build system " + + "configuration bug?), path: " + maintSvcDir.path); + } + + return true; +} + +/** + * Waits for the applications that are launched by the maintenance service to + * stop. + */ +function waitServiceApps() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + // maintenanceservice_installer.exe is started async during updates. + waitForApplicationStop("maintenanceservice_installer.exe"); + // maintenanceservice_tmp.exe is started async from the service installer. + waitForApplicationStop("maintenanceservice_tmp.exe"); + // In case the SCM thinks the service is stopped, but process still exists. + waitForApplicationStop("maintenanceservice.exe"); +} + +/** + * Waits for the maintenance service to stop. + */ +function waitForServiceStop(aFailTest) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + waitServiceApps(); + debugDump("waiting for the maintenance service to stop if necessary"); + // Use the helper bin to ensure the service is stopped. If not stopped, then + // wait for the service to stop (at most 120 seconds). + let args = ["wait-for-service-stop", "MozillaMaintenance", "120"]; + let exitValue = runTestHelperSync(args); + Assert.notEqual(exitValue, 0xEE, + "the maintenance service should exist"); + if (exitValue != 0) { + if (aFailTest) { + Assert.ok(false, "the maintenance service should stop, process exit " + + "value: " + exitValue); + } + logTestInfo("maintenance service did not stop which may cause test " + + "failures later, process exit value: " + exitValue); + } else { + debugDump("service stopped"); + } + waitServiceApps(); +} + +/** + * Waits for the specified application to stop. + * + * @param aApplication + * The application binary name to wait until it has stopped. + */ +function waitForApplicationStop(aApplication) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + debugDump("waiting for " + aApplication + " to stop if necessary"); + // Use the helper bin to ensure the application is stopped. If not stopped, + // then wait for it to stop (at most 120 seconds). + let args = ["wait-for-application-exit", aApplication, "120"]; + let exitValue = runTestHelperSync(args); + Assert.equal(exitValue, 0, + "the process should have stopped, process name: " + + aApplication); +} + + +/** + * Gets the platform specific shell binary that is launched using nsIProcess and + * in turn launches a binary used for the test (e.g. application, updater, + * etc.). A shell is used so debug console output can be redirected to a file so + * it doesn't end up in the test log. + * + * @return nsIFile for the shell binary to launch using nsIProcess. + */ +function getLaunchBin() { + let launchBin; + if (IS_WIN) { + launchBin = Services.dirsvc.get("WinD", Ci.nsIFile); + launchBin.append("System32"); + launchBin.append("cmd.exe"); + } else { + launchBin = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsILocalFile); + launchBin.initWithPath("/bin/sh"); + } + Assert.ok(launchBin.exists(), + MSG_SHOULD_EXIST + getMsgPath(launchBin.path)); + + return launchBin; +} + + +/** + * Locks a Windows directory. + * + * @param aDirPath + * The test file object that describes the file to make in use. + */ +function lockDirectory(aDirPath) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + debugDump("start - locking installation directory"); + const LPCWSTR = ctypes.char16_t.ptr; + const DWORD = ctypes.uint32_t; + const LPVOID = ctypes.voidptr_t; + const GENERIC_READ = 0x80000000; + const FILE_SHARE_READ = 1; + const FILE_SHARE_WRITE = 2; + const OPEN_EXISTING = 3; + const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + const INVALID_HANDLE_VALUE = LPVOID(0xffffffff); + let kernel32 = ctypes.open("kernel32"); + let CreateFile = kernel32.declare("CreateFileW", ctypes.default_abi, + LPVOID, LPCWSTR, DWORD, DWORD, + LPVOID, DWORD, DWORD, LPVOID); + gHandle = CreateFile(aDirPath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, LPVOID(0), + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, LPVOID(0)); + Assert.notEqual(gHandle.toString(), INVALID_HANDLE_VALUE.toString(), + "the handle should not equal INVALID_HANDLE_VALUE"); + kernel32.close(); + debugDump("finish - locking installation directory"); +} + +/** + * Launches the test helper binary to make it in use for updater tests and then + * calls waitForHelperSleep. + * + * @param aTestFile + * The test file object that describes the file to make in use. + */ +function runHelperFileInUse(aRelPath, aCopyTestHelper) { + logTestInfo("aRelPath: " + aRelPath); + // Launch an existing file so it is in use during the update. + let helperBin = getTestDirFile(FILE_HELPER_BIN); + let fileInUseBin = getApplyDirFile(aRelPath); + if (aCopyTestHelper) { + fileInUseBin.remove(false); + helperBin.copyTo(fileInUseBin.parent, fileInUseBin.leafName); + } + fileInUseBin.permissions = PERMS_DIRECTORY; + let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s", + HELPER_SLEEP_TIMEOUT]; + let fileInUseProcess = Cc["@mozilla.org/process/util;1"]. + createInstance(Ci.nsIProcess); + fileInUseProcess.init(fileInUseBin); + fileInUseProcess.run(false, args, args.length); + + do_execute_soon(waitForHelperSleep); +} + +/** + * Launches the test helper binary and locks a file specified on the command + * line for updater tests and then calls waitForHelperSleep. + * + * @param aTestFile + * The test file object that describes the file to lock. + */ +function runHelperLockFile(aTestFile) { + // Exclusively lock an existing file so it is in use during the update. + let helperBin = getTestDirFile(FILE_HELPER_BIN); + let helperDestDir = getApplyDirFile(DIR_RESOURCES); + helperBin.copyTo(helperDestDir, FILE_HELPER_BIN); + helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN); + // Strip off the first two directories so the path has to be from the helper's + // working directory. + let lockFileRelPath = aTestFile.relPathDir.split("/"); + if (IS_MACOSX) { + lockFileRelPath = lockFileRelPath.slice(2); + } + lockFileRelPath = lockFileRelPath.join("/") + "/" + aTestFile.fileName; + let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s", + HELPER_SLEEP_TIMEOUT, lockFileRelPath]; + let helperProcess = Cc["@mozilla.org/process/util;1"]. + createInstance(Ci.nsIProcess); + helperProcess.init(helperBin); + helperProcess.run(false, args, args.length); + + do_execute_soon(waitForHelperSleep); +} + +/** + * Helper function that waits until the helper has completed its operations and + * calls waitForHelperSleepFinished when it is finished. + */ +function waitForHelperSleep() { + gTimeoutRuns++; + // Give the lock file process time to lock the file before updating otherwise + // this test can fail intermittently on Windows debug builds. + let output = getApplyDirFile(DIR_RESOURCES + "output", true); + if (readFile(output) != "sleeping\n") { + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the helper to " + + "finish its operation. Path: " + output.path); + } + // Uses do_timeout instead of do_execute_soon to lessen log spew. + do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperSleep); + return; + } + try { + output.remove(false); + } catch (e) { + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the helper " + + "message file to no longer be in use. Path: " + output.path); + } + debugDump("failed to remove file. Path: " + output.path); + // Uses do_timeout instead of do_execute_soon to lessen log spew. + do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperSleep); + return; + } + waitForHelperSleepFinished(); +} + +/** + * Helper function that waits until the helper has finished its operations + * before calling waitForHelperFinishFileUnlock to verify that the helper's + * input and output directories are no longer in use. + */ +function waitForHelperFinished() { + // Give the lock file process time to lock the file before updating otherwise + // this test can fail intermittently on Windows debug builds. + let output = getApplyDirFile(DIR_RESOURCES + "output", true); + if (readFile(output) != "finished\n") { + // Uses do_timeout instead of do_execute_soon to lessen log spew. + do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperFinished); + return; + } + // Give the lock file process time to unlock the file before deleting the + // input and output files. + waitForHelperFinishFileUnlock(); +} + +/** + * Helper function that waits until the helper's input and output files are no + * longer in use before calling waitForHelperExitFinished. + */ +function waitForHelperFinishFileUnlock() { + try { + let output = getApplyDirFile(DIR_RESOURCES + "output", true); + if (output.exists()) { + output.remove(false); + } + let input = getApplyDirFile(DIR_RESOURCES + "input", true); + if (input.exists()) { + input.remove(false); + } + } catch (e) { + // Give the lock file process time to unlock the file before deleting the + // input and output files. + do_execute_soon(waitForHelperFinishFileUnlock); + return; + } + do_execute_soon(waitForHelperExitFinished); +} + +/** + * Helper function to tell the helper to finish and exit its sleep state. + */ +function waitForHelperExit() { + let input = getApplyDirFile(DIR_RESOURCES + "input", true); + writeFile(input, "finish\n"); + waitForHelperFinished(); +} + +/** + * Helper function for updater binary tests that creates the files and + * directories used by the test. + * + * @param aMarFile + * The mar file for the update test. + * @param aPostUpdateAsync + * When null the updater.ini is not created otherwise this parameter + * is passed to createUpdaterINI. + * @param aPostUpdateExeRelPathPrefix + * When aPostUpdateAsync null this value is ignored otherwise it is + * passed to createUpdaterINI. + */ +function setupUpdaterTest(aMarFile, aPostUpdateAsync, + aPostUpdateExeRelPathPrefix = "") { + let updatesPatchDir = getUpdatesPatchDir(); + if (!updatesPatchDir.exists()) { + updatesPatchDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY); + } + // Copy the mar that will be applied + let mar = getTestDirFile(aMarFile); + mar.copyToFollowingLinks(updatesPatchDir, FILE_UPDATE_MAR); + + let helperBin = getTestDirFile(FILE_HELPER_BIN); + helperBin.permissions = PERMS_DIRECTORY; + let afterApplyBinDir = getApplyDirFile(DIR_RESOURCES, true); + helperBin.copyToFollowingLinks(afterApplyBinDir, gCallbackBinFile); + helperBin.copyToFollowingLinks(afterApplyBinDir, gPostUpdateBinFile); + + gTestFiles.forEach(function SUT_TF_FE(aTestFile) { + if (aTestFile.originalFile || aTestFile.originalContents) { + let testDir = getApplyDirFile(aTestFile.relPathDir, true); + if (!testDir.exists()) { + testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY); + } + + let testFile; + if (aTestFile.originalFile) { + testFile = getTestDirFile(aTestFile.originalFile); + testFile.copyToFollowingLinks(testDir, aTestFile.fileName); + testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName); + } else { + testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName, + true); + writeFile(testFile, aTestFile.originalContents); + } + + // Skip these tests on Windows since chmod doesn't really set permissions + // on Windows. + if (!IS_WIN && aTestFile.originalPerms) { + testFile.permissions = aTestFile.originalPerms; + // Store the actual permissions on the file for reference later after + // setting the permissions. + if (!aTestFile.comparePerms) { + aTestFile.comparePerms = testFile.permissions; + } + } + } + }); + + // Add the test directory that will be updated for a successful update or left + // in the initial state for a failed update. + gTestDirs.forEach(function SUT_TD_FE(aTestDir) { + let testDir = getApplyDirFile(aTestDir.relPathDir, true); + if (!testDir.exists()) { + testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY); + } + + if (aTestDir.files) { + aTestDir.files.forEach(function SUT_TD_F_FE(aTestFile) { + let testFile = getApplyDirFile(aTestDir.relPathDir + aTestFile, true); + if (!testFile.exists()) { + testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE); + } + }); + } + + if (aTestDir.subDirs) { + aTestDir.subDirs.forEach(function SUT_TD_SD_FE(aSubDir) { + let testSubDir = getApplyDirFile(aTestDir.relPathDir + aSubDir, true); + if (!testSubDir.exists()) { + testSubDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY); + } + + if (aTestDir.subDirFiles) { + aTestDir.subDirFiles.forEach(function SUT_TD_SDF_FE(aTestFile) { + let testFile = getApplyDirFile(aTestDir.relPathDir + aSubDir + aTestFile, true); + if (!testFile.exists()) { + testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE); + } + }); + } + }); + } + }); + + setupActiveUpdate(); + + if (aPostUpdateAsync !== null) { + createUpdaterINI(aPostUpdateAsync, aPostUpdateExeRelPathPrefix); + } + + setupAppFilesAsync(); +} + +/** + * Helper function for updater binary tests that creates the update-settings.ini + * file. + */ +function createUpdateSettingsINI() { + let ini = getApplyDirFile(DIR_RESOURCES + FILE_UPDATE_SETTINGS_INI, true); + writeFile(ini, UPDATE_SETTINGS_CONTENTS); +} + +/** + * Helper function for updater binary tests that creates the updater.ini + * file. + * + * @param aIsExeAsync + * True or undefined if the post update process should be async. If + * undefined ExeAsync will not be added to the updater.ini file in + * order to test the default launch behavior which is async. + * @param aExeRelPathPrefix + * A string to prefix the ExeRelPath values in the updater.ini. + */ +function createUpdaterINI(aIsExeAsync, aExeRelPathPrefix) { + let exeArg = "ExeArg=post-update-async\n"; + let exeAsync = ""; + if (aIsExeAsync !== undefined) { + if (aIsExeAsync) { + exeAsync = "ExeAsync=true\n"; + } else { + exeArg = "ExeArg=post-update-sync\n"; + exeAsync = "ExeAsync=false\n"; + } + } + + if (aExeRelPathPrefix && IS_WIN) { + aExeRelPathPrefix = aExeRelPathPrefix.replace("/", "\\"); + } + + let exeRelPathMac = "ExeRelPath=" + aExeRelPathPrefix + DIR_RESOURCES + + gPostUpdateBinFile + "\n"; + let exeRelPathWin = "ExeRelPath=" + aExeRelPathPrefix + gPostUpdateBinFile + "\n"; + let updaterIniContents = "[Strings]\n" + + "Title=Update Test\n" + + "Info=Running update test " + gTestID + "\n\n" + + "[PostUpdateMac]\n" + + exeRelPathMac + + exeArg + + exeAsync + + "\n" + + "[PostUpdateWin]\n" + + exeRelPathWin + + exeArg + + exeAsync; + let updaterIni = getApplyDirFile(DIR_RESOURCES + FILE_UPDATER_INI, true); + writeFile(updaterIni, updaterIniContents); +} + +/** + * Gets the message log path used for assert checks to lessen the length printed + * to the log file. + * + * @param aPath + * The path to shorten for the log file. + * @return the message including the shortened path for the log file. + */ +function getMsgPath(aPath) { + return ", path: " + replaceLogPaths(aPath); +} + +/** + * Helper function that replaces the common part of paths in the update log's + * contents with for paths to the the test directory and + * for paths to the update directory. This is needed since + * Assert.equal will truncate what it prints to the xpcshell log file. + * + * @param aLogContents + * The update log file's contents. + * @return the log contents with the paths replaced. + */ +function replaceLogPaths(aLogContents) { + let logContents = aLogContents; + // Remove the majority of the path up to the test directory. This is needed + // since Assert.equal won't print long strings to the test logs. + let testDirPath = do_get_file(gTestID, false).path; + if (IS_WIN) { + // Replace \\ with \\\\ so the regexp works. + testDirPath = testDirPath.replace(/\\/g, "\\\\"); + } + logContents = logContents.replace(new RegExp(testDirPath, "g"), + "/" + gTestID); + let updatesDirPath = getMockUpdRootD().path; + if (IS_WIN) { + // Replace \\ with \\\\ so the regexp works. + updatesDirPath = updatesDirPath.replace(/\\/g, "\\\\"); + } + logContents = logContents.replace(new RegExp(updatesDirPath, "g"), + "/" + gTestID); + if (IS_WIN) { + // Replace \ with / + logContents = logContents.replace(/\\/g, "/"); + } + return logContents; +} + +/** + * Helper function for updater binary tests for verifying the contents of the + * update log after a successful update. + * + * @param aCompareLogFile + * The log file to compare the update log with. + * @param aStaged + * If the update log file is for a staged update. + * @param aReplace + * If the update log file is for a replace update. + * @param aExcludeDistDir + * Removes lines containing the distribution directory from the log + * file to compare the update log with. + */ +function checkUpdateLogContents(aCompareLogFile, aStaged = false, + aReplace = false, aExcludeDistDir = false) { + if (IS_UNIX && !IS_MACOSX) { + // The order that files are returned when enumerating the file system on + // Linux is not deterministic so skip checking the logs. + return; + } + + let updateLog = getUpdateLog(FILE_LAST_UPDATE_LOG); + let updateLogContents = readFileBytes(updateLog); + + // The channel-prefs.js is defined in gTestFilesCommon which will always be + // located to the end of gTestFiles when it is present. + if (gTestFiles.length > 1 && + gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" && + !gTestFiles[gTestFiles.length - 1].originalContents) { + updateLogContents = updateLogContents.replace(/.*defaults\/.*/g, ""); + } + + if (gTestFiles.length > 2 && + gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI && + !gTestFiles[gTestFiles.length - 2].originalContents) { + updateLogContents = updateLogContents.replace(/.*update-settings.ini.*/g, ""); + } + + // Skip the source/destination lines since they contain absolute paths. + // These could be changed to relative paths using and + // + updateLogContents = updateLogContents.replace(/PATCH DIRECTORY.*/g, ""); + updateLogContents = updateLogContents.replace(/INSTALLATION DIRECTORY.*/g, ""); + updateLogContents = updateLogContents.replace(/WORKING DIRECTORY.*/g, ""); + // Skip lines that log failed attempts to open the callback executable. + updateLogContents = updateLogContents.replace(/NS_main: callback app file .*/g, ""); + + if (IS_MACOSX) { + // Skip lines that log moving the distribution directory for Mac v2 signing. + updateLogContents = updateLogContents.replace(/Moving old [^\n]*\nrename_file: .*/g, ""); + updateLogContents = updateLogContents.replace(/New distribution directory .*/g, ""); + } + + if (IS_WIN) { + // The FindFile results when enumerating the filesystem on Windows is not + // determistic so the results matching the following need to be fixed. + let re = new RegExp("([^\n]* 7\/7text1[^\n]*)\n" + + "([^\n]* 7\/7text0[^\n]*)\n", "g"); + updateLogContents = updateLogContents.replace(re, "$2\n$1\n"); + } + + if (aReplace) { + // Remove the lines which contain absolute paths + updateLogContents = updateLogContents.replace(/^Begin moving.*$/mg, ""); + updateLogContents = updateLogContents.replace(/^ensure_remove: failed to remove file: .*$/mg, ""); + updateLogContents = updateLogContents.replace(/^ensure_remove_recursive: unable to remove directory: .*$/mg, ""); + updateLogContents = updateLogContents.replace(/^Removing tmpDir failed, err: -1$/mg, ""); + updateLogContents = updateLogContents.replace(/^remove_recursive_on_reboot: .*$/mg, ""); + } + + // Remove carriage returns. + updateLogContents = updateLogContents.replace(/\r/g, ""); + // Replace error codes since they are different on each platform. + updateLogContents = updateLogContents.replace(/, err:.*\n/g, "\n"); + // Replace to make the log parsing happy. + updateLogContents = updateLogContents.replace(/non-fatal error /g, ""); + // Remove consecutive newlines + updateLogContents = updateLogContents.replace(/\n+/g, "\n"); + // Remove leading and trailing newlines + updateLogContents = updateLogContents.replace(/^\n|\n$/g, ""); + // Replace the log paths with and + updateLogContents = replaceLogPaths(updateLogContents); + + let compareLogContents = ""; + if (aCompareLogFile) { + compareLogContents = readFileBytes(getTestDirFile(aCompareLogFile)); + } + + if (aStaged) { + compareLogContents = PERFORMING_STAGED_UPDATE + "\n" + compareLogContents; + } + + // The channel-prefs.js is defined in gTestFilesCommon which will always be + // located to the end of gTestFiles. + if (gTestFiles.length > 1 && + gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" && + !gTestFiles[gTestFiles.length - 1].originalContents) { + compareLogContents = compareLogContents.replace(/.*defaults\/.*/g, ""); + } + + if (gTestFiles.length > 2 && + gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI && + !gTestFiles[gTestFiles.length - 2].originalContents) { + compareLogContents = compareLogContents.replace(/.*update-settings.ini.*/g, ""); + } + + if (aExcludeDistDir) { + compareLogContents = compareLogContents.replace(/.*distribution\/.*/g, ""); + } + + // Remove leading and trailing newlines + compareLogContents = compareLogContents.replace(/\n+/g, "\n"); + // Remove leading and trailing newlines + compareLogContents = compareLogContents.replace(/^\n|\n$/g, ""); + + // Don't write the contents of the file to the log to reduce log spam + // unless there is a failure. + if (compareLogContents == updateLogContents) { + Assert.ok(true, "the update log contents" + MSG_SHOULD_EQUAL); + } else { + logTestInfo("the update log contents are not correct"); + logUpdateLog(FILE_LAST_UPDATE_LOG); + let aryLog = updateLogContents.split("\n"); + let aryCompare = compareLogContents.split("\n"); + // Pushing an empty string to both arrays makes it so either array's length + // can be used in the for loop below without going out of bounds. + aryLog.push(""); + aryCompare.push(""); + // xpcshell tests won't display the entire contents so log the first + // incorrect line. + for (let i = 0; i < aryLog.length; ++i) { + if (aryLog[i] != aryCompare[i]) { + logTestInfo("the first incorrect line in the update log is: " + + aryLog[i]); + Assert.equal(aryLog[i], aryCompare[i], + "the update log contents" + MSG_SHOULD_EQUAL); + } + } + // This should never happen! + do_throw("Unable to find incorrect update log contents!"); + } +} + +/** + * Helper function to check if the update log contains a string. + * + * @param aCheckString + * The string to check if the update log contains. + */ +function checkUpdateLogContains(aCheckString) { + let updateLog = getUpdateLog(FILE_LAST_UPDATE_LOG); + let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n"); + updateLogContents = replaceLogPaths(updateLogContents); + Assert.notEqual(updateLogContents.indexOf(aCheckString), -1, + "the update log contents should contain value: " + + aCheckString); +} + +/** + * Helper function for updater binary tests for verifying the state of files and + * directories after a successful update. + * + * @param aGetFileFunc + * The function used to get the files in the directory to be checked. + * @param aStageDirExists + * If true the staging directory will be tested for existence and if + * false the staging directory will be tested for non-existence. + * @param aToBeDeletedDirExists + * On Windows, if true the tobedeleted directory will be tested for + * existence and if false the tobedeleted directory will be tested for + * non-existence. On all othere platforms it will be tested for + * non-existence. + */ +function checkFilesAfterUpdateSuccess(aGetFileFunc, aStageDirExists = false, + aToBeDeletedDirExists = false) { + debugDump("testing contents of files after a successful update"); + gTestFiles.forEach(function CFAUS_TF_FE(aTestFile) { + let testFile = aGetFileFunc(aTestFile.relPathDir + aTestFile.fileName, true); + debugDump("testing file: " + testFile.path); + if (aTestFile.compareFile || aTestFile.compareContents) { + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + // Skip these tests on Windows since chmod doesn't really set permissions + // on Windows. + if (!IS_WIN && aTestFile.comparePerms) { + // Check if the permssions as set in the complete mar file are correct. + Assert.equal(testFile.permissions & 0xfff, + aTestFile.comparePerms & 0xfff, + "the file permissions" + MSG_SHOULD_EQUAL); + } + + let fileContents1 = readFileBytes(testFile); + let fileContents2 = aTestFile.compareFile ? + readFileBytes(getTestDirFile(aTestFile.compareFile)) : + aTestFile.compareContents; + // Don't write the contents of the file to the log to reduce log spam + // unless there is a failure. + if (fileContents1 == fileContents2) { + Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL); + } else { + Assert.equal(fileContents1, fileContents2, + "the file contents" + MSG_SHOULD_EQUAL); + } + } else { + Assert.ok(!testFile.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path)); + } + }); + + debugDump("testing operations specified in removed-files were performed " + + "after a successful update"); + gTestDirs.forEach(function CFAUS_TD_FE(aTestDir) { + let testDir = aGetFileFunc(aTestDir.relPathDir, true); + debugDump("testing directory: " + testDir.path); + if (aTestDir.dirRemoved) { + Assert.ok(!testDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(testDir.path)); + } else { + Assert.ok(testDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(testDir.path)); + + if (aTestDir.files) { + aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) { + let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true); + if (aTestDir.filesRemoved) { + Assert.ok(!testFile.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path)); + } else { + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + } + }); + } + + if (aTestDir.subDirs) { + aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) { + let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true); + Assert.ok(testSubDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(testSubDir.path)); + if (aTestDir.subDirFiles) { + aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) { + let testFile = aGetFileFunc(aTestDir.relPathDir + + aSubDir + aTestFile, true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + }); + } + }); + } + } + }); + + checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists, + aToBeDeletedDirExists); +} + +/** + * Helper function for updater binary tests for verifying the state of files and + * directories after a failed update. + * + * @param aGetFileFunc + * The function used to get the files in the directory to be checked. + * @param aStageDirExists + * If true the staging directory will be tested for existence and if + * false the staging directory will be tested for non-existence. + * @param aToBeDeletedDirExists + * On Windows, if true the tobedeleted directory will be tested for + * existence and if false the tobedeleted directory will be tested for + * non-existence. On all othere platforms it will be tested for + * non-existence. + */ +function checkFilesAfterUpdateFailure(aGetFileFunc, aStageDirExists = false, + aToBeDeletedDirExists = false) { + debugDump("testing contents of files after a failed update"); + gTestFiles.forEach(function CFAUF_TF_FE(aTestFile) { + let testFile = aGetFileFunc(aTestFile.relPathDir + aTestFile.fileName, true); + debugDump("testing file: " + testFile.path); + if (aTestFile.compareFile || aTestFile.compareContents) { + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + // Skip these tests on Windows since chmod doesn't really set permissions + // on Windows. + if (!IS_WIN && aTestFile.comparePerms) { + // Check the original permssions are retained on the file. + Assert.equal(testFile.permissions & 0xfff, + aTestFile.comparePerms & 0xfff, + "the file permissions" + MSG_SHOULD_EQUAL); + } + + let fileContents1 = readFileBytes(testFile); + let fileContents2 = aTestFile.compareFile ? + readFileBytes(getTestDirFile(aTestFile.compareFile)) : + aTestFile.compareContents; + // Don't write the contents of the file to the log to reduce log spam + // unless there is a failure. + if (fileContents1 == fileContents2) { + Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL); + } else { + Assert.equal(fileContents1, fileContents2, + "the file contents" + MSG_SHOULD_EQUAL); + } + } else { + Assert.ok(!testFile.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path)); + } + }); + + debugDump("testing operations specified in removed-files were not " + + "performed after a failed update"); + gTestDirs.forEach(function CFAUF_TD_FE(aTestDir) { + let testDir = aGetFileFunc(aTestDir.relPathDir, true); + Assert.ok(testDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(testDir.path)); + + if (aTestDir.files) { + aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) { + let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + }); + } + + if (aTestDir.subDirs) { + aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) { + let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true); + Assert.ok(testSubDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(testSubDir.path)); + if (aTestDir.subDirFiles) { + aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) { + let testFile = aGetFileFunc(aTestDir.relPathDir + + aSubDir + aTestFile, true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + }); + } + }); + } + }); + + checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists, + aToBeDeletedDirExists); +} + +/** + * Helper function for updater binary tests for verifying the state of common + * files and directories after a successful or failed update. + * + * @param aGetFileFunc + * the function used to get the files in the directory to be checked. + * @param aStageDirExists + * If true the staging directory will be tested for existence and if + * false the staging directory will be tested for non-existence. + * @param aToBeDeletedDirExists + * On Windows, if true the tobedeleted directory will be tested for + * existence and if false the tobedeleted directory will be tested for + * non-existence. On all othere platforms it will be tested for + * non-existence. + */ +function checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists, + aToBeDeletedDirExists) { + debugDump("testing extra directories"); + let stageDir = getStageDirFile(null, true); + if (aStageDirExists) { + Assert.ok(stageDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(stageDir.path)); + } else { + Assert.ok(!stageDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path)); + } + + let toBeDeletedDirExists = IS_WIN ? aToBeDeletedDirExists : false; + let toBeDeletedDir = getApplyDirFile(DIR_TOBEDELETED, true); + if (toBeDeletedDirExists) { + Assert.ok(toBeDeletedDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(toBeDeletedDir.path)); + } else { + Assert.ok(!toBeDeletedDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(toBeDeletedDir.path)); + } + + let updatingDir = getApplyDirFile("updating", true); + Assert.ok(!updatingDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path)); + + if (stageDir.exists()) { + updatingDir = stageDir.clone(); + updatingDir.append("updating"); + Assert.ok(!updatingDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path)); + } + + debugDump("testing backup files should not be left behind in the " + + "application directory"); + let applyToDir = getApplyDirFile(null, true); + checkFilesInDirRecursive(applyToDir, checkForBackupFiles); + + if (stageDir.exists()) { + debugDump("testing backup files should not be left behind in the " + + "staging directory"); + applyToDir = getApplyDirFile(null, true); + checkFilesInDirRecursive(stageDir, checkForBackupFiles); + } +} + +/** + * Helper function for updater binary tests for verifying the contents of the + * updater callback application log which should contain the arguments passed to + * the callback application. + */ +function checkCallbackLog() { + let appLaunchLog = getApplyDirFile(DIR_RESOURCES + gCallbackArgs[1], true); + if (!appLaunchLog.exists()) { + // Uses do_timeout instead of do_execute_soon to lessen log spew. + do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog); + return; + } + + let expectedLogContents = gCallbackArgs.join("\n") + "\n"; + let logContents = readFile(appLaunchLog); + // It is possible for the log file contents check to occur before the log file + // contents are completely written so wait until the contents are the expected + // value. If the contents are never the expected value then the test will + // fail by timing out after gTimeoutRuns is greater than MAX_TIMEOUT_RUNS or + // the test harness times out the test. + if (logContents != expectedLogContents) { + gTimeoutRuns++; + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + logTestInfo("callback log contents are not correct"); + // This file doesn't contain full paths so there is no need to call + // replaceLogPaths. + let aryLog = logContents.split("\n"); + let aryCompare = expectedLogContents.split("\n"); + // Pushing an empty string to both arrays makes it so either array's length + // can be used in the for loop below without going out of bounds. + aryLog.push(""); + aryCompare.push(""); + // xpcshell tests won't display the entire contents so log the incorrect + // line. + for (let i = 0; i < aryLog.length; ++i) { + if (aryLog[i] != aryCompare[i]) { + logTestInfo("the first incorrect line in the callback log is: " + + aryLog[i]); + Assert.equal(aryLog[i], aryCompare[i], + "the callback log contents" + MSG_SHOULD_EQUAL); + } + } + // This should never happen! + do_throw("Unable to find incorrect callback log contents!"); + } + // Uses do_timeout instead of do_execute_soon to lessen log spew. + do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog); + return; + } + Assert.ok(true, "the callback log contents" + MSG_SHOULD_EQUAL); + + waitForFilesInUse(); +} + +/** + * Helper function for updater binary tests for getting the log and running + * files created by the test helper binary file when called with the post-update + * command line argument. + * + * @param aSuffix + * The string to append to the post update test helper binary path. + */ +function getPostUpdateFile(aSuffix) { + return getApplyDirFile(DIR_RESOURCES + gPostUpdateBinFile + aSuffix, true); +} + +/** + * Checks the contents of the updater post update binary log. When completed + * checkPostUpdateAppLogFinished will be called. + */ +function checkPostUpdateAppLog() { + // Only Mac OS X and Windows support post update. + if (IS_MACOSX || IS_WIN) { + gTimeoutRuns++; + let postUpdateLog = getPostUpdateFile(".log"); + if (!postUpdateLog.exists()) { + debugDump("postUpdateLog does not exist. Path: " + postUpdateLog.path); + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the post update " + + "process to create the post update log. Path: " + + postUpdateLog.path); + } + do_execute_soon(checkPostUpdateAppLog); + return; + } + + let logContents = readFile(postUpdateLog); + // It is possible for the log file contents check to occur before the log file + // contents are completely written so wait until the contents are the expected + // value. If the contents are never the expected value then the test will + // fail by timing out after gTimeoutRuns is greater than MAX_TIMEOUT_RUNS or + // the test harness times out the test. + if (logContents != "post-update\n") { + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the post update " + + "process to create the expected contents in the post update log. Path: " + + postUpdateLog.path); + } + do_execute_soon(checkPostUpdateAppLog); + return; + } + Assert.ok(true, "the post update log contents" + MSG_SHOULD_EQUAL); + } + + do_execute_soon(checkPostUpdateAppLogFinished); +} + +/** + * Helper function to check if a file is in use on Windows by making a copy of + * a file and attempting to delete the original file. If the deletion is + * successful the copy of the original file is renamed to the original file's + * name and if the deletion is not successful the copy of the original file is + * deleted. + * + * @param aFile + * An nsIFile for the file to be checked if it is in use. + * @return true if the file can't be deleted and false otherwise. + */ +function isFileInUse(aFile) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + if (!aFile.exists()) { + debugDump("file does not exist, path: " + aFile.path); + return false; + } + + let fileBak = aFile.parent; + fileBak.append(aFile.leafName + ".bak"); + try { + if (fileBak.exists()) { + fileBak.remove(false); + } + aFile.copyTo(aFile.parent, fileBak.leafName); + aFile.remove(false); + fileBak.moveTo(aFile.parent, aFile.leafName); + debugDump("file is not in use, path: " + aFile.path); + return false; + } catch (e) { + debugDump("file in use, path: " + aFile.path + ", exception: " + e); + try { + if (fileBak.exists()) { + fileBak.remove(false); + } + } catch (ex) { + logTestInfo("unable to remove backup file, path: " + + fileBak.path + ", exception: " + ex); + } + } + return true; +} + +/** + * Waits until files that are in use that break tests are no longer in use and + * then calls doTestFinish to end the test. + */ +function waitForFilesInUse() { + if (IS_WIN) { + let fileNames = [FILE_APP_BIN, FILE_UPDATER_BIN, + FILE_MAINTENANCE_SERVICE_INSTALLER_BIN]; + for (let i = 0; i < fileNames.length; ++i) { + let file = getApplyDirFile(fileNames[i], true); + if (isFileInUse(file)) { + do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForFilesInUse); + return; + } + } + } + + debugDump("calling doTestFinish"); + doTestFinish(); +} + +/** + * Helper function for updater binary tests for verifying there are no update + * backup files left behind after an update. + * + * @param aFile + * An nsIFile to check if it has moz-backup for its extension. + */ +function checkForBackupFiles(aFile) { + Assert.notEqual(getFileExtension(aFile), "moz-backup", + "the file's extension should not equal moz-backup" + + getMsgPath(aFile.path)); +} + +/** + * Helper function for updater binary tests for recursively enumerating a + * directory and calling a callback function with the file as a parameter for + * each file found. + * + * @param aDir + * A nsIFile for the directory to be deleted + * @param aCallback + * A callback function that will be called with the file as a + * parameter for each file found. + */ +function checkFilesInDirRecursive(aDir, aCallback) { + if (!aDir.exists()) { + do_throw("Directory must exist!"); + } + + let dirEntries = aDir.directoryEntries; + while (dirEntries.hasMoreElements()) { + let entry = dirEntries.getNext().QueryInterface(Ci.nsIFile); + + if (entry.exists()) { + if (entry.isDirectory()) { + checkFilesInDirRecursive(entry, aCallback); + } else { + aCallback(entry); + } + } + } +} + + +/** + * Helper function to override the update prompt component to verify whether it + * is called or not. + * + * @param aCallback + * The callback to call if the update prompt component is called. + */ +function overrideUpdatePrompt(aCallback) { + Cu.import("resource://testing-common/MockRegistrar.jsm"); + MockRegistrar.register("@mozilla.org/updates/update-prompt;1", UpdatePrompt, [aCallback]); +} + +function UpdatePrompt(aCallback) { + this._callback = aCallback; + + let fns = ["checkForUpdates", "showUpdateAvailable", "showUpdateDownloaded", + "showUpdateError", "showUpdateHistory", "showUpdateInstalled"]; + + fns.forEach(function UP_fns(aPromptFn) { + UpdatePrompt.prototype[aPromptFn] = function() { + if (!this._callback) { + return; + } + + let callback = this._callback[aPromptFn]; + if (!callback) { + return; + } + + callback.apply(this._callback, + Array.prototype.slice.call(arguments)); + }; + }); +} + +UpdatePrompt.prototype = { + flags: Ci.nsIClassInfo.SINGLETON, + getScriptableHelper: () => null, + getInterfaces: function(aCount) { + let interfaces = [Ci.nsISupports, Ci.nsIUpdatePrompt]; + aCount.value = interfaces.length; + return interfaces; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIClassInfo, Ci.nsIUpdatePrompt]) +}; + +/* Update check listener */ +const updateCheckListener = { + onProgress: function UCL_onProgress(aRequest, aPosition, aTotalSize) { + }, + + onCheckComplete: function UCL_onCheckComplete(aRequest, aUpdates, aUpdateCount) { + gRequestURL = aRequest.channel.originalURI.spec; + gUpdateCount = aUpdateCount; + gUpdates = aUpdates; + debugDump("url = " + gRequestURL + ", " + + "request.status = " + aRequest.status + ", " + + "updateCount = " + aUpdateCount); + // Use a timeout to allow the XHR to complete + do_execute_soon(gCheckFunc); + }, + + onError: function UCL_onError(aRequest, aUpdate) { + gRequestURL = aRequest.channel.originalURI.spec; + gStatusCode = aRequest.status; + if (gStatusCode == 0) { + gStatusCode = aRequest.channel.QueryInterface(Ci.nsIRequest).status; + } + gStatusText = aUpdate.statusText ? aUpdate.statusText : null; + debugDump("url = " + gRequestURL + ", " + + "request.status = " + gStatusCode + ", " + + "update.statusText = " + gStatusText); + // Use a timeout to allow the XHR to complete + do_execute_soon(gCheckFunc.bind(null, aRequest, aUpdate)); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener]) +}; + +/* Update download listener - nsIRequestObserver */ +const downloadListener = { + onStartRequest: function DL_onStartRequest(aRequest, aContext) { + }, + + onProgress: function DL_onProgress(aRequest, aContext, aProgress, aMaxProgress) { + }, + + onStatus: function DL_onStatus(aRequest, aContext, aStatus, aStatusText) { + }, + + onStopRequest: function DL_onStopRequest(aRequest, aContext, aStatus) { + gStatusResult = aStatus; + // Use a timeout to allow the request to complete + do_execute_soon(gCheckFunc); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver, + Ci.nsIProgressEventSink]) +}; + +/** + * Helper for starting the http server used by the tests + */ +function start_httpserver() { + let dir = getTestDirFile(); + debugDump("http server directory path: " + dir.path); + + if (!dir.isDirectory()) { + do_throw("A file instead of a directory was specified for HttpServer " + + "registerDirectory! Path: " + dir.path); + } + + let { HttpServer } = Cu.import("resource://testing-common/httpd.js", {}); + gTestserver = new HttpServer(); + gTestserver.registerDirectory("/", dir); + gTestserver.registerPathHandler("/" + gHTTPHandlerPath, pathHandler); + gTestserver.start(-1); + let testserverPort = gTestserver.identity.primaryPort; + gURLData = URL_HOST + ":" + testserverPort + "/"; + debugDump("http server port = " + testserverPort); +} + +/** + * Custom path handler for the http server + * + * @param aMetadata + * The http metadata for the request. + * @param aResponse + * The http response for the request. + */ +function pathHandler(aMetadata, aResponse) { + aResponse.setHeader("Content-Type", "text/xml", false); + aResponse.setStatusLine(aMetadata.httpVersion, gResponseStatusCode, "OK"); + aResponse.bodyOutputStream.write(gResponseBody, gResponseBody.length); +} + +/** + * Helper for stopping the http server used by the tests + * + * @param aCallback + * The callback to call after stopping the http server. + */ +function stop_httpserver(aCallback) { + Assert.ok(!!aCallback, "the aCallback parameter should be defined"); + gTestserver.stop(aCallback); +} + +/** + * Creates an nsIXULAppInfo + * + * @param aID + * The ID of the test application + * @param aName + * A name for the test application + * @param aVersion + * The version of the application + * @param aPlatformVersion + * The gecko version of the application + */ +function createAppInfo(aID, aName, aVersion, aPlatformVersion) { + const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1"; + const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}"); + let ifaces = [Ci.nsIXULAppInfo, Ci.nsIXULRuntime]; + if (IS_WIN) { + ifaces.push(Ci.nsIWinAppHelper); + } + const XULAppInfo = { + vendor: APP_INFO_VENDOR, + name: aName, + ID: aID, + version: aVersion, + appBuildID: "2007010101", + platformVersion: aPlatformVersion, + platformBuildID: "2007010101", + inSafeMode: false, + logConsoleErrors: true, + OS: "XPCShell", + XPCOMABI: "noarch-spidermonkey", + + QueryInterface: XPCOMUtils.generateQI(ifaces) + }; + + const XULAppInfoFactory = { + createInstance: function(aOuter, aIID) { + if (aOuter == null) { + return XULAppInfo.QueryInterface(aIID); + } + throw Cr.NS_ERROR_NO_AGGREGATION; + } + }; + + let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo", + XULAPPINFO_CONTRACTID, XULAppInfoFactory); +} + +/** + * Returns the platform specific arguments used by nsIProcess when launching + * the application. + * + * @param aExtraArgs (optional) + * An array of extra arguments to append to the default arguments. + * @return an array of arguments to be passed to nsIProcess. + * + * Note: a shell is necessary to pipe the application's console output which + * would otherwise pollute the xpcshell log. + * + * Command line arguments used when launching the application: + * -no-remote prevents shell integration from being affected by an existing + * application process. + * -test-process-updates makes the application exit after being relaunched by + * the updater. + * the platform specific string defined by PIPE_TO_NULL to output both stdout + * and stderr to null. This is needed to prevent output from the application + * from ending up in the xpchsell log. + */ +function getProcessArgs(aExtraArgs) { + if (!aExtraArgs) { + aExtraArgs = []; + } + + let appBinPath = getApplyDirFile(DIR_MACOS + FILE_APP_BIN, false).path; + if (/ /.test(appBinPath)) { + appBinPath = '"' + appBinPath + '"'; + } + + let args; + if (IS_UNIX) { + let launchScript = getLaunchScript(); + // Precreate the script with executable permissions + launchScript.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_DIRECTORY); + + let scriptContents = "#! /bin/sh\n"; + scriptContents += appBinPath + " -no-remote -test-process-updates " + + aExtraArgs.join(" ") + " " + PIPE_TO_NULL; + writeFile(launchScript, scriptContents); + debugDump("created " + launchScript.path + " containing:\n" + + scriptContents); + args = [launchScript.path]; + } else { + args = ["/D", "/Q", "/C", appBinPath, "-no-remote", "-test-process-updates"]. + concat(aExtraArgs).concat([PIPE_TO_NULL]); + } + return args; +} + +/** + * Gets a file path for the application to dump its arguments into. This is used + * to verify that a callback application is launched. + * + * @return the file for the application to dump its arguments into. + */ +function getAppArgsLogPath() { + let appArgsLog = do_get_file("/" + gTestID + "_app_args_log", true); + if (appArgsLog.exists()) { + appArgsLog.remove(false); + } + let appArgsLogPath = appArgsLog.path; + if (/ /.test(appArgsLogPath)) { + appArgsLogPath = '"' + appArgsLogPath + '"'; + } + return appArgsLogPath; +} + +/** + * Gets the nsIFile reference for the shell script to launch the application. If + * the file exists it will be removed by this function. + * + * @return the nsIFile for the shell script to launch the application. + */ +function getLaunchScript() { + let launchScript = do_get_file("/" + gTestID + "_launch.sh", true); + if (launchScript.exists()) { + launchScript.remove(false); + } + return launchScript; +} + +/** + * Makes GreD, XREExeF, and UpdRootD point to unique file system locations so + * xpcshell tests can run in parallel and to keep the environment clean. + */ +function adjustGeneralPaths() { + let dirProvider = { + getFile: function AGP_DP_getFile(aProp, aPersistent) { + aPersistent.value = true; + switch (aProp) { + case NS_GRE_DIR: + if (gUseTestAppDir) { + return getApplyDirFile(DIR_RESOURCES, true); + } + break; + case NS_GRE_BIN_DIR: + if (gUseTestAppDir) { + return getApplyDirFile(DIR_MACOS, true); + } + break; + case XRE_EXECUTABLE_FILE: + if (gUseTestAppDir) { + return getApplyDirFile(DIR_MACOS + FILE_APP_BIN, true); + } + break; + case XRE_UPDATE_ROOT_DIR: + return getMockUpdRootD(); + } + return null; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]) + }; + let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService); + ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_DIR); + ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_BIN_DIR); + ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE); + ds.registerProvider(dirProvider); + do_register_cleanup(function AGP_cleanup() { + debugDump("start - unregistering directory provider"); + + if (gAppTimer) { + debugDump("start - cancel app timer"); + gAppTimer.cancel(); + gAppTimer = null; + debugDump("finish - cancel app timer"); + } + + if (gProcess && gProcess.isRunning) { + debugDump("start - kill process"); + try { + gProcess.kill(); + } catch (e) { + debugDump("kill process failed. Exception: " + e); + } + gProcess = null; + debugDump("finish - kill process"); + } + + if (gHandle) { + try { + debugDump("start - closing handle"); + let kernel32 = ctypes.open("kernel32"); + let CloseHandle = kernel32.declare("CloseHandle", ctypes.default_abi, + ctypes.bool, /* return*/ + ctypes.voidptr_t /* handle*/); + if (!CloseHandle(gHandle)) { + debugDump("call to CloseHandle failed"); + } + kernel32.close(); + gHandle = null; + debugDump("finish - closing handle"); + } catch (e) { + debugDump("call to CloseHandle failed. Exception: " + e); + } + } + + // Call end_test first before the directory provider is unregistered + if (typeof end_test == typeof Function) { + debugDump("calling end_test"); + end_test(); + } + + ds.unregisterProvider(dirProvider); + cleanupTestCommon(); + + debugDump("finish - unregistering directory provider"); + }); +} + +/** + * The timer callback to kill the process if it takes too long. + */ +const gAppTimerCallback = { + notify: function TC_notify(aTimer) { + gAppTimer = null; + if (gProcess.isRunning) { + logTestInfo("attempting to kill process"); + gProcess.kill(); + } + Assert.ok(false, "launch application timer expired"); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback]) +}; + +/** + * Launches an application to apply an update. + */ +function runUpdateUsingApp(aExpectedStatus) { + /** + * The observer for the call to nsIProcess:runAsync. When completed + * runUpdateFinished will be called. + */ + const processObserver = { + observe: function PO_observe(aSubject, aTopic, aData) { + debugDump("topic: " + aTopic + ", process exitValue: " + + gProcess.exitValue); + resetEnvironment(); + if (gAppTimer) { + gAppTimer.cancel(); + gAppTimer = null; + } + Assert.equal(gProcess.exitValue, 0, + "the application process exit value should be 0"); + Assert.equal(aTopic, "process-finished", + "the application process observer topic should be " + + "process-finished"); + + if (IS_SERVICE_TEST) { + waitForServiceStop(false); + } + + do_execute_soon(afterAppExits); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + }; + + function afterAppExits() { + gTimeoutRuns++; + + if (IS_WIN) { + waitForApplicationStop(FILE_UPDATER_BIN); + } + + let status; + try { + status = readStatusFile(); + } catch (e) { + logTestInfo("error reading status file, exception: " + e); + } + // Don't proceed until the update's status is the expected value. + if (status != aExpectedStatus) { + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + logUpdateLog(FILE_UPDATE_LOG); + do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " + + "status to equal: " + + aExpectedStatus + + ", current status: " + status); + } else { + do_timeout(FILE_IN_USE_TIMEOUT_MS, afterAppExits); + } + return; + } + + // Don't check for an update log when the code in nsUpdateDriver.cpp skips + // updating. + if (aExpectedStatus != STATE_PENDING && + aExpectedStatus != STATE_PENDING_SVC && + aExpectedStatus != STATE_APPLIED && + aExpectedStatus != STATE_APPLIED_SVC) { + // Don't proceed until the update log has been created. + let log = getUpdateLog(FILE_UPDATE_LOG); + if (!log.exists()) { + if (gTimeoutRuns > MAX_TIMEOUT_RUNS) { + do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " + + "log to be created. Path: " + log.path); + } + do_timeout(FILE_IN_USE_TIMEOUT_MS, afterAppExits); + return; + } + } + + do_execute_soon(runUpdateFinished); + } + + debugDump("start - launching application to apply update"); + + let appBin = getApplyDirFile(DIR_MACOS + FILE_APP_BIN, false); + + let launchBin = getLaunchBin(); + let args = getProcessArgs(); + debugDump("launching " + launchBin.path + " " + args.join(" ")); + + gProcess = Cc["@mozilla.org/process/util;1"]. + createInstance(Ci.nsIProcess); + gProcess.init(launchBin); + + gAppTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + gAppTimer.initWithCallback(gAppTimerCallback, APP_TIMER_TIMEOUT, + Ci.nsITimer.TYPE_ONE_SHOT); + + setEnvironment(); + debugDump("launching application"); + gProcess.runAsync(args, args.length, processObserver); + + debugDump("finish - launching application to apply update"); +} + +/** + * Sets the environment that will be used by the application process when it is + * launched. + */ +function setEnvironment() { + // Prevent setting the environment more than once. + if (gShouldResetEnv !== undefined) { + return; + } + + gShouldResetEnv = true; + + // See bug 1279108. + if (gEnv.exists("ASAN_OPTIONS")) { + gASanOptions = gEnv.get("ASAN_OPTIONS"); + gEnv.set("ASAN_OPTIONS", gASanOptions + ":detect_leaks=0"); + } else { + gEnv.set("ASAN_OPTIONS", "detect_leaks=0"); + } + + if (IS_WIN && !gEnv.exists("XRE_NO_WINDOWS_CRASH_DIALOG")) { + gAddedEnvXRENoWindowsCrashDialog = true; + debugDump("setting the XRE_NO_WINDOWS_CRASH_DIALOG environment " + + "variable to 1... previously it didn't exist"); + gEnv.set("XRE_NO_WINDOWS_CRASH_DIALOG", "1"); + } + + if (IS_UNIX) { + let appGreBinDir = gGREBinDirOrig.clone(); + let envGreBinDir = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsILocalFile); + let shouldSetEnv = true; + if (IS_MACOSX) { + if (gEnv.exists("DYLD_LIBRARY_PATH")) { + gEnvDyldLibraryPath = gEnv.get("DYLD_LIBRARY_PATH"); + envGreBinDir.initWithPath(gEnvDyldLibraryPath); + if (envGreBinDir.path == appGreBinDir.path) { + gEnvDyldLibraryPath = null; + shouldSetEnv = false; + } + } + + if (shouldSetEnv) { + debugDump("setting DYLD_LIBRARY_PATH environment variable value to " + + appGreBinDir.path); + gEnv.set("DYLD_LIBRARY_PATH", appGreBinDir.path); + } + } else { + if (gEnv.exists("LD_LIBRARY_PATH")) { + gEnvLdLibraryPath = gEnv.get("LD_LIBRARY_PATH"); + envGreBinDir.initWithPath(gEnvLdLibraryPath); + if (envGreBinDir.path == appGreBinDir.path) { + gEnvLdLibraryPath = null; + shouldSetEnv = false; + } + } + + if (shouldSetEnv) { + debugDump("setting LD_LIBRARY_PATH environment variable value to " + + appGreBinDir.path); + gEnv.set("LD_LIBRARY_PATH", appGreBinDir.path); + } + } + } + + if (gEnv.exists("XPCOM_MEM_LEAK_LOG")) { + gEnvXPCOMMemLeakLog = gEnv.get("XPCOM_MEM_LEAK_LOG"); + debugDump("removing the XPCOM_MEM_LEAK_LOG environment variable... " + + "previous value " + gEnvXPCOMMemLeakLog); + gEnv.set("XPCOM_MEM_LEAK_LOG", ""); + } + + if (gEnv.exists("XPCOM_DEBUG_BREAK")) { + gEnvXPCOMDebugBreak = gEnv.get("XPCOM_DEBUG_BREAK"); + debugDump("setting the XPCOM_DEBUG_BREAK environment variable to " + + "warn... previous value " + gEnvXPCOMDebugBreak); + } else { + debugDump("setting the XPCOM_DEBUG_BREAK environment variable to " + + "warn... previously it didn't exist"); + } + + gEnv.set("XPCOM_DEBUG_BREAK", "warn"); + + if (IS_SERVICE_TEST) { + debugDump("setting MOZ_NO_SERVICE_FALLBACK environment variable to 1"); + gEnv.set("MOZ_NO_SERVICE_FALLBACK", "1"); + } +} + +/** + * Sets the environment back to the original values after launching the + * application. + */ +function resetEnvironment() { + // Prevent resetting the environment more than once. + if (gShouldResetEnv !== true) { + return; + } + + gShouldResetEnv = false; + + // Restore previous ASAN_OPTIONS if there were any. + gEnv.set("ASAN_OPTIONS", gASanOptions ? gASanOptions : ""); + + if (gEnvXPCOMMemLeakLog) { + debugDump("setting the XPCOM_MEM_LEAK_LOG environment variable back to " + + gEnvXPCOMMemLeakLog); + gEnv.set("XPCOM_MEM_LEAK_LOG", gEnvXPCOMMemLeakLog); + } + + if (gEnvXPCOMDebugBreak) { + debugDump("setting the XPCOM_DEBUG_BREAK environment variable back to " + + gEnvXPCOMDebugBreak); + gEnv.set("XPCOM_DEBUG_BREAK", gEnvXPCOMDebugBreak); + } else if (gEnv.exists("XPCOM_DEBUG_BREAK")) { + debugDump("clearing the XPCOM_DEBUG_BREAK environment variable"); + gEnv.set("XPCOM_DEBUG_BREAK", ""); + } + + if (IS_UNIX) { + if (IS_MACOSX) { + if (gEnvDyldLibraryPath) { + debugDump("setting DYLD_LIBRARY_PATH environment variable value " + + "back to " + gEnvDyldLibraryPath); + gEnv.set("DYLD_LIBRARY_PATH", gEnvDyldLibraryPath); + } else if (gEnvDyldLibraryPath !== null) { + debugDump("removing DYLD_LIBRARY_PATH environment variable"); + gEnv.set("DYLD_LIBRARY_PATH", ""); + } + } else if (gEnvLdLibraryPath) { + debugDump("setting LD_LIBRARY_PATH environment variable value back " + + "to " + gEnvLdLibraryPath); + gEnv.set("LD_LIBRARY_PATH", gEnvLdLibraryPath); + } else if (gEnvLdLibraryPath !== null) { + debugDump("removing LD_LIBRARY_PATH environment variable"); + gEnv.set("LD_LIBRARY_PATH", ""); + } + } + + if (IS_WIN && gAddedEnvXRENoWindowsCrashDialog) { + debugDump("removing the XRE_NO_WINDOWS_CRASH_DIALOG environment " + + "variable"); + gEnv.set("XRE_NO_WINDOWS_CRASH_DIALOG", ""); + } + + if (IS_SERVICE_TEST) { + debugDump("removing MOZ_NO_SERVICE_FALLBACK environment variable"); + gEnv.set("MOZ_NO_SERVICE_FALLBACK", ""); + } +} diff --git a/toolkit/mozapps/update/tests/moz.build b/toolkit/mozapps/update/tests/moz.build new file mode 100644 index 000000000..842ec7f90 --- /dev/null +++ b/toolkit/mozapps/update/tests/moz.build @@ -0,0 +1,101 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +HAS_MISC_RULE = True + +FINAL_TARGET = '_tests/xpcshell/toolkit/mozapps/update/tests/data' + +MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini'] + +XPCSHELL_TESTS_MANIFESTS += [ + 'unit_aus_update/xpcshell.ini', + 'unit_base_updater/xpcshell.ini' +] + +if CONFIG['MOZ_MAINTENANCE_SERVICE']: + XPCSHELL_TESTS_MANIFESTS += ['unit_service_updater/xpcshell.ini'] + +SimplePrograms([ + 'TestAUSHelper', + 'TestAUSReadStrings', +]) + +LOCAL_INCLUDES += [ + '/toolkit/mozapps/update', + '/toolkit/mozapps/update/common', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + USE_LIBS += [ + 'updatecommon-standalone', + ] + + OS_LIBS += [ + 'shlwapi', + ] +else: + USE_LIBS += [ + 'updatecommon', + ] + +for var in ('MOZ_APP_NAME', 'MOZ_APP_BASENAME', 'MOZ_APP_DISPLAYNAME', + 'MOZ_APP_VENDOR', 'BIN_SUFFIX', 'MOZ_DEBUG'): + DEFINES[var] = CONFIG[var] + +DEFINES['NS_NO_XPCOM'] = True + +if CONFIG['MOZ_MAINTENANCE_SERVICE']: + DEFINES['MOZ_MAINTENANCE_SERVICE'] = CONFIG['MOZ_MAINTENANCE_SERVICE'] + +# For debugging purposes only +#DEFINES['DISABLE_UPDATER_AUTHENTICODE_CHECK'] = True + +if CONFIG['OS_ARCH'] == 'WINNT': + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + USE_STATIC_LIBS = True + if CONFIG['GNU_CC']: + WIN32_EXE_LDFLAGS += ['-municode'] + +TEST_HARNESS_FILES.testing.mochitest.chrome.toolkit.mozapps.update.tests.data += [ + 'data/shared.js', + 'data/sharedUpdateXML.js', + 'data/simple.mar', +] + +FINAL_TARGET_FILES += [ + 'data/complete.exe', + 'data/complete.mar', + 'data/complete.png', + 'data/complete_log_success_mac', + 'data/complete_log_success_win', + 'data/complete_mac.mar', + 'data/complete_precomplete', + 'data/complete_precomplete_mac', + 'data/complete_removed-files', + 'data/complete_removed-files_mac', + 'data/complete_update_manifest', + 'data/old_version.mar', + 'data/partial.exe', + 'data/partial.mar', + 'data/partial.png', + 'data/partial_log_failure_mac', + 'data/partial_log_failure_win', + 'data/partial_log_success_mac', + 'data/partial_log_success_win', + 'data/partial_mac.mar', + 'data/partial_precomplete', + 'data/partial_precomplete_mac', + 'data/partial_removed-files', + 'data/partial_removed-files_mac', + 'data/partial_update_manifest', + 'data/replace_log_success', + 'data/shared.js', + 'data/sharedUpdateXML.js', + 'data/simple.mar', + 'data/wrong_product_channel.mar', + 'data/xpcshellUtilsAUS.js', +] diff --git a/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js b/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js new file mode 100644 index 000000000..1985df959 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js @@ -0,0 +1,138 @@ +/* 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/. + */ + +function run_test() { + setupTestCommon(); + + // Verify write access to the custom app dir + debugDump("testing write access to the application directory"); + let testFile = getCurrentProcessDir(); + testFile.append("update_write_access_test"); + testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE); + Assert.ok(testFile.exists(), MSG_SHOULD_EXIST); + testFile.remove(false); + Assert.ok(!testFile.exists(), MSG_SHOULD_NOT_EXIST); + + standardInit(); + + if (IS_WIN) { + // Create a mutex to prevent being able to check for or apply updates. + debugDump("attempting to create mutex"); + let handle = createMutex(getPerInstallationMutexName()); + Assert.ok(!!handle, "the update mutex should have been created"); + + // Check if available updates cannot be checked for when there is a mutex + // for this installation. + Assert.ok(!gAUS.canCheckForUpdates, "should not be able to check for " + + "updates when there is an update mutex"); + + // Check if updates cannot be applied when there is a mutex for this + // installation. + Assert.ok(!gAUS.canApplyUpdates, "should not be able to apply updates " + + "when there is an update mutex"); + + debugDump("destroying mutex"); + closeHandle(handle); + } + + // Check if available updates can be checked for + Assert.ok(gAUS.canCheckForUpdates, "should be able to check for updates"); + // Check if updates can be applied + Assert.ok(gAUS.canApplyUpdates, "should be able to apply updates"); + + if (IS_WIN) { + // Attempt to create a mutex when application update has already created one + // with the same name. + debugDump("attempting to create mutex"); + let handle = createMutex(getPerInstallationMutexName()); + + Assert.ok(!handle, "should not be able to create the update mutex when " + + "the application has created the update mutex"); + } + + doTestFinish(); +} + +/** + * Determines a unique mutex name for the installation. + * + * @return Global mutex path. + */ +function getPerInstallationMutexName() { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let hasher = Cc["@mozilla.org/security/hash;1"]. + createInstance(Ci.nsICryptoHash); + hasher.init(hasher.SHA1); + + let exeFile = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsILocalFile); + + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + let data = converter.convertToByteArray(exeFile.path.toLowerCase()); + + hasher.update(data, data.length); + return "Global\\MozillaUpdateMutex-" + hasher.finish(true); +} + +/** + * Closes a Win32 handle. + * + * @param aHandle + * The handle to close. + */ +function closeHandle(aHandle) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + let lib = ctypes.open("kernel32.dll"); + let CloseHandle = lib.declare("CloseHandle", + ctypes.winapi_abi, + ctypes.int32_t, /* success */ + ctypes.void_t.ptr); /* handle */ + CloseHandle(aHandle); + lib.close(); +} + +/** + * Creates a mutex. + * + * @param aName + * The name for the mutex. + * @return The Win32 handle to the mutex. + */ +function createMutex(aName) { + if (!IS_WIN) { + do_throw("Windows only function called by a different platform!"); + } + + const INITIAL_OWN = 1; + const ERROR_ALREADY_EXISTS = 0xB7; + let lib = ctypes.open("kernel32.dll"); + let CreateMutexW = lib.declare("CreateMutexW", + ctypes.winapi_abi, + ctypes.void_t.ptr, /* return handle */ + ctypes.void_t.ptr, /* security attributes */ + ctypes.int32_t, /* initial owner */ + ctypes.char16_t.ptr); /* name */ + + let handle = CreateMutexW(null, INITIAL_OWN, aName); + lib.close(); + let alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS; + if (handle && !handle.isNull() && alreadyExists) { + closeHandle(handle); + handle = null; + } + + if (handle && handle.isNull()) { + handle = null; + } + + return handle; +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js new file mode 100644 index 000000000..a0a95af1b --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js @@ -0,0 +1,46 @@ +/* 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/. + */ + +/* General Update Manager Tests */ + +function run_test() { + setupTestCommon(); + + debugDump("testing removal of an active update for a channel that is not" + + "valid due to switching channels (Bug 486275)."); + + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_DOWNLOADING); + let updates = getLocalUpdateString(patches, null, null, "version 1.0", "1.0"); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_DOWNLOADING); + + patches = getLocalPatchString(null, null, null, null, null, null, + STATE_FAILED); + updates = getLocalUpdateString(patches, null, "Existing", "version 3.0", + "3.0", "3.0", null, null, null, null, + getString("patchApplyFailure")); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), false); + + setUpdateChannel("original_channel"); + + standardInit(); + + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager update count" + MSG_SHOULD_EQUAL); + let update = gUpdateManager.getUpdateAt(0); + Assert.equal(update.name, "Existing", + "the update's name" + MSG_SHOULD_EQUAL); + + Assert.ok(!gUpdateManager.activeUpdate, + "there should not be an active update"); + // Verify that the active-update.xml file has had the update from the old + // channel removed. + let file = getUpdatesXMLFile(true); + Assert.equal(readFile(file), getLocalUpdatesXMLString(""), + "the contents of active-update.xml" + MSG_SHOULD_EQUAL); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js new file mode 100644 index 000000000..fc4f09787 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + +function run_test() { + setupTestCommon(); + + debugDump("testing cleanup of an update download in progress for an " + + "older version of the application on startup (Bug 485624)"); + + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_DOWNLOADING); + let updates = getLocalUpdateString(patches, null, null, "version 0.9", "0.9"); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_DOWNLOADING); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + + standardInit(); + + Assert.ok(!gUpdateManager.activeUpdate, + "there should not be an active update"); + Assert.equal(gUpdateManager.updateCount, 0, + "the update manager update count" + MSG_SHOULD_EQUAL); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js new file mode 100644 index 000000000..b2d8ecbc6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +function run_test() { + setupTestCommon(); + + debugDump("testing removal of an update download in progress for the " + + "same version of the application with the same application " + + "build id on startup (Bug 536547)"); + + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_DOWNLOADING); + let updates = getLocalUpdateString(patches, null, null, "version 1.0", "1.0", + "2007010101"); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_DOWNLOADING); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + + standardInit(); + + Assert.ok(!gUpdateManager.activeUpdate, + "there should not be an active update"); + Assert.equal(gUpdateManager.updateCount, 0, + "the update manager update count" + MSG_SHOULD_EQUAL); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js new file mode 100644 index 000000000..13e4aeaf6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function run_test() { + setupTestCommon(); + + debugDump("testing update cleanup when reading the status file returns " + + "STATUS_NONE and the update xml has an update with " + + "STATE_DOWNLOADING (Bug 539717)."); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_DOWNLOADING); + let updates = getLocalUpdateString(patches); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_NONE); + + standardInit(); + + let dir = getUpdatesDir(); + dir.append(DIR_PATCH); + Assert.ok(dir.exists(), MSG_SHOULD_EXIST); + + let statusFile = dir.clone(); + statusFile.append(FILE_UPDATE_STATUS); + Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST); + + Assert.ok(!gUpdateManager.activeUpdate, + "there should not be an active update"); + Assert.equal(gUpdateManager.updateCount, 0, + "the update manager update count" + MSG_SHOULD_EQUAL); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js new file mode 100644 index 000000000..7661da82d --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function run_test() { + setupTestCommon(); + + debugDump("testing update cleanup when reading the status file returns " + + "STATUS_NONE, the version file is for a newer version, and the " + + "update xml has an update with STATE_PENDING (Bug 601701)."); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_PENDING); + let updates = getLocalUpdateString(patches); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeVersionFile("99.9"); + + standardInit(); + + // Check that there is no activeUpdate first so the updates directory is + // cleaned up by the UpdateManager before the remaining tests. + Assert.ok(!gUpdateManager.activeUpdate, + "there should not be an active update"); + Assert.equal(gUpdateManager.updateCount, 0, + "the update manager update count" + MSG_SHOULD_EQUAL); + + let dir = getUpdatesDir(); + dir.append(DIR_PATCH); + Assert.ok(dir.exists(), MSG_SHOULD_EXIST); + + let versionFile = dir.clone(); + versionFile.append(FILE_UPDATE_VERSION); + Assert.ok(!versionFile.exists(), MSG_SHOULD_NOT_EXIST); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js new file mode 100644 index 000000000..d683b9931 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +function run_test() { + setupTestCommon(); + + debugDump("testing that the update.log is moved after a successful update"); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_PENDING); + let updates = getLocalUpdateString(patches); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_SUCCEEDED); + + let log = getUpdateLog(FILE_UPDATE_LOG); + writeFile(log, "Last Update Log"); + + standardInit(); + + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal(readFile(log), "Last Update Log", + "the last update log contents" + MSG_SHOULD_EQUAL); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + + let dir = getUpdatesPatchDir(); + Assert.ok(dir.exists(), MSG_SHOULD_EXIST); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js new file mode 100644 index 000000000..8be93d0ff --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js @@ -0,0 +1,45 @@ +/* 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/. + */ + +function run_test() { + setupTestCommon(); + + debugDump("testing update logs are first in first out deleted"); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_PENDING); + let updates = getLocalUpdateString(patches); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_SUCCEEDED); + + let log = getUpdateLog(FILE_LAST_UPDATE_LOG); + writeFile(log, "Backup Update Log"); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + writeFile(log, "To Be Deleted Backup Update Log"); + + log = getUpdateLog(FILE_UPDATE_LOG); + writeFile(log, "Last Update Log"); + + standardInit(); + + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal(readFile(log), "Last Update Log", + "the last update log contents" + MSG_SHOULD_EQUAL); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal(readFile(log), "Backup Update Log", + "the backup update log contents" + MSG_SHOULD_EQUAL); + + let dir = getUpdatesPatchDir(); + Assert.ok(dir.exists(), MSG_SHOULD_EXIST); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js new file mode 100644 index 000000000..b715fb56e --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js @@ -0,0 +1,161 @@ +/* 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/. + */ + +var gNextRunFunc; +var gExpectedStatusResult; + +function run_test() { + // The network code that downloads the mar file accesses the profile to cache + // the download, but the profile is only available after calling + // do_get_profile in xpcshell tests. This prevents an error from being logged. + do_get_profile(); + + setupTestCommon(); + + debugDump("testing mar download and mar hash verification"); + + Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false); + start_httpserver(); + setUpdateURL(gURLData + gHTTPHandlerPath); + standardInit(); + // Only perform the non hash check tests when mar signing is enabled since the + // update service doesn't perform hash checks when mar signing is enabled. + if (MOZ_VERIFY_MAR_SIGNATURE) { + do_execute_soon(run_test_pt11); + } else { + do_execute_soon(run_test_pt1); + } +} + +// The HttpServer must be stopped before calling do_test_finished +function finish_test() { + stop_httpserver(doTestFinish); +} + +// Helper function for testing mar downloads that have the correct size +// specified in the update xml. +function run_test_helper_pt1(aMsg, aExpectedStatusResult, aNextRunFunc) { + gUpdates = null; + gUpdateCount = null; + gStatusResult = null; + gCheckFunc = check_test_helper_pt1_1; + gNextRunFunc = aNextRunFunc; + gExpectedStatusResult = aExpectedStatusResult; + debugDump(aMsg, Components.stack.caller); + gUpdateChecker.checkForUpdates(updateCheckListener, true); +} + +function check_test_helper_pt1_1() { + Assert.equal(gUpdateCount, 1, + "the update count" + MSG_SHOULD_EQUAL); + gCheckFunc = check_test_helper_pt1_2; + let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount); + let state = gAUS.downloadUpdate(bestUpdate, false); + if (state == STATE_NONE || state == STATE_FAILED) { + do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state); + } + gAUS.addDownloadListener(downloadListener); +} + +function check_test_helper_pt1_2() { + Assert.equal(gStatusResult, gExpectedStatusResult, + "the download status result" + MSG_SHOULD_EQUAL); + gAUS.removeDownloadListener(downloadListener); + gNextRunFunc(); +} + +function setResponseBody(aHashFunction, aHashValue, aSize) { + let patches = getRemotePatchString(null, null, + aHashFunction, aHashValue, aSize); + let updates = getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); +} + +// mar download with a valid MD5 hash +function run_test_pt1() { + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with a valid MD5 hash", + Cr.NS_OK, run_test_pt2); +} + +// mar download with an invalid MD5 hash +function run_test_pt2() { + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR + "0"); + run_test_helper_pt1("mar download with an invalid MD5 hash", + Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt3); +} + +// mar download with a valid SHA1 hash +function run_test_pt3() { + setResponseBody("SHA1", SHA1_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with a valid SHA1 hash", + Cr.NS_OK, run_test_pt4); +} + +// mar download with an invalid SHA1 hash +function run_test_pt4() { + setResponseBody("SHA1", SHA1_HASH_SIMPLE_MAR + "0"); + run_test_helper_pt1("mar download with an invalid SHA1 hash", + Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt5); +} + +// mar download with a valid SHA256 hash +function run_test_pt5() { + setResponseBody("SHA256", SHA256_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with a valid SHA256 hash", + Cr.NS_OK, run_test_pt6); +} + +// mar download with an invalid SHA256 hash +function run_test_pt6() { + setResponseBody("SHA256", SHA256_HASH_SIMPLE_MAR + "0"); + run_test_helper_pt1("mar download with an invalid SHA256 hash", + Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt7); +} + +// mar download with a valid SHA384 hash +function run_test_pt7() { + setResponseBody("SHA384", SHA384_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with a valid SHA384 hash", + Cr.NS_OK, run_test_pt8); +} + +// mar download with an invalid SHA384 hash +function run_test_pt8() { + setResponseBody("SHA384", SHA384_HASH_SIMPLE_MAR + "0"); + run_test_helper_pt1("mar download with an invalid SHA384 hash", + Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt9); +} + +// mar download with a valid SHA512 hash +function run_test_pt9() { + setResponseBody("SHA512", SHA512_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with a valid SHA512 hash", + Cr.NS_OK, run_test_pt10); +} + +// mar download with an invalid SHA512 hash +function run_test_pt10() { + setResponseBody("SHA512", SHA512_HASH_SIMPLE_MAR + "0"); + run_test_helper_pt1("mar download with an invalid SHA512 hash", + Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt11); +} + +// mar download with the mar not found +function run_test_pt11() { + let patches = getRemotePatchString(null, gURLData + "missing.mar"); + let updates = getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("mar download with the mar not found", + Cr.NS_ERROR_UNEXPECTED, run_test_pt12); +} + +// mar download with a valid MD5 hash but invalid file size +function run_test_pt12() { + const arbitraryFileSize = 1024000; + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR, arbitraryFileSize); + run_test_helper_pt1("mar download with a valid MD5 hash but invalid file size", + Cr.NS_ERROR_UNEXPECTED, finish_test); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js new file mode 100644 index 000000000..159033792 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +Components.utils.import("resource://testing-common/MockRegistrar.jsm"); + +const WindowWatcher = { + getNewPrompter: function WW_getNewPrompter(aParent) { + Assert.ok(!aParent, + "the aParent parameter should not be defined"); + return { + alert: function WW_GNP_alert(aTitle, aText) { + let title = getString("updaterIOErrorTitle"); + Assert.equal(aTitle, title, + "the ui string for title" + MSG_SHOULD_EQUAL); + let text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg", + [Services.appinfo.name, + Services.appinfo.name], 2); + Assert.equal(aText, text, + "the ui string for message" + MSG_SHOULD_EQUAL); + + doTestFinish(); + } + }; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]) +}; + +function run_test() { + setupTestCommon(); + + debugDump("testing download a complete on partial failure. Calling " + + "nsIUpdatePrompt::showUpdateError should call getNewPrompter " + + "and alert on the object returned by getNewPrompter when the " + + "update.state == " + STATE_FAILED + " and the update.errorCode " + + "== " + WRITE_ERROR + " (Bug 595059)."); + + Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false); + + let windowWatcherCID = + MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", + WindowWatcher); + do_register_cleanup(() => { + MockRegistrar.unregister(windowWatcherCID); + }); + + standardInit(); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + let url = URL_HOST + "/" + FILE_COMPLETE_MAR; + let patches = getLocalPatchString("complete", url, null, null, null, null, + STATE_FAILED); + let updates = getLocalUpdateString(patches, null, null, "version 1.0", "1.0", + null, null, null, null, url); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_FAILED); + + reloadUpdateManagerData(); + + let update = gUpdateManager.activeUpdate; + update.errorCode = WRITE_ERROR; + let prompter = Cc["@mozilla.org/updates/update-prompt;1"]. + createInstance(Ci.nsIUpdatePrompt); + prompter.showUpdateError(update); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js new file mode 100644 index 000000000..ef2da26af --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js @@ -0,0 +1,225 @@ +/* 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/. + */ + +/* General MAR File Download Tests */ + +Components.utils.import("resource://testing-common/MockRegistrar.jsm"); +const INC_CONTRACT_ID = "@mozilla.org/network/incremental-download;1"; + +// gIncrementalDownloadErrorType is used to loop through each of the connection +// error types in the Mock incremental downloader. +var gIncrementalDownloadErrorType = 0; + +var gNextRunFunc; +var gExpectedStatusResult; + +function run_test() { + setupTestCommon(); + + debugDump("testing mar downloads, mar hash verification, and " + + "mar download interrupted recovery"); + + Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false); + start_httpserver(); + setUpdateURL(gURLData + gHTTPHandlerPath); + standardInit(); + do_execute_soon(run_test_pt1); +} + +// The HttpServer must be stopped before calling do_test_finished +function finish_test() { + stop_httpserver(doTestFinish); +} + +// Helper function for testing mar downloads that have the correct size +// specified in the update xml. +function run_test_helper_pt1(aMsg, aExpectedStatusResult, aNextRunFunc) { + gUpdates = null; + gUpdateCount = null; + gStatusResult = null; + gCheckFunc = check_test_helper_pt1_1; + gNextRunFunc = aNextRunFunc; + gExpectedStatusResult = aExpectedStatusResult; + debugDump(aMsg, Components.stack.caller); + gUpdateChecker.checkForUpdates(updateCheckListener, true); +} + +function check_test_helper_pt1_1() { + Assert.equal(gUpdateCount, 1, + "the update count" + MSG_SHOULD_EQUAL); + gCheckFunc = check_test_helper_pt1_2; + let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount); + let state = gAUS.downloadUpdate(bestUpdate, false); + if (state == STATE_NONE || state == STATE_FAILED) { + do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state); + } + gAUS.addDownloadListener(downloadListener); +} + +function check_test_helper_pt1_2() { + Assert.equal(gStatusResult, gExpectedStatusResult, + "the download status result" + MSG_SHOULD_EQUAL); + gAUS.removeDownloadListener(downloadListener); + gNextRunFunc(); +} + +function setResponseBody(aHashFunction, aHashValue, aSize) { + let patches = getRemotePatchString(null, null, + aHashFunction, aHashValue, aSize); + let updates = getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); +} + +function initMockIncrementalDownload() { + let incrementalDownloadCID = + MockRegistrar.register(INC_CONTRACT_ID, IncrementalDownload); + do_register_cleanup(() => { + MockRegistrar.unregister(incrementalDownloadCID); + }); +} + +/* This Mock incremental downloader is used to verify that connection + * interrupts work correctly in updater code. The implementation of + * the mock incremental downloader is very simple, it simply copies + * the file to the destination location. + */ + +function IncrementalDownload() { + this.wrappedJSObject = this; +} + +IncrementalDownload.prototype = { + /* nsIIncrementalDownload */ + init: function(uri, file, chunkSize, intervalInSeconds) { + this._destination = file; + this._URI = uri; + this._finalURI = uri; + }, + + start: function(observer, ctxt) { + let tm = Cc["@mozilla.org/thread-manager;1"]. + getService(Ci.nsIThreadManager); + // Do the actual operation async to give a chance for observers + // to add themselves. + tm.mainThread.dispatch(function() { + this._observer = observer.QueryInterface(Ci.nsIRequestObserver); + this._ctxt = ctxt; + this._observer.onStartRequest(this, this._ctxt); + let mar = getTestDirFile(FILE_SIMPLE_MAR); + mar.copyTo(this._destination.parent, this._destination.leafName); + let status = Cr.NS_OK; + switch (gIncrementalDownloadErrorType++) { + case 0: + status = Cr.NS_ERROR_NET_RESET; + break; + case 1: + status = Cr.NS_ERROR_CONNECTION_REFUSED; + break; + case 2: + status = Cr.NS_ERROR_NET_RESET; + break; + case 3: + status = Cr.NS_OK; + break; + case 4: + status = Cr.NS_ERROR_OFFLINE; + // After we report offline, we want to eventually show offline + // status being changed to online. + let tm2 = Cc["@mozilla.org/thread-manager;1"]. + getService(Ci.nsIThreadManager); + tm2.mainThread.dispatch(function() { + Services.obs.notifyObservers(gAUS, + "network:offline-status-changed", + "online"); + }, Ci.nsIThread.DISPATCH_NORMAL); + break; + } + this._observer.onStopRequest(this, this._ctxt, status); + }.bind(this), Ci.nsIThread.DISPATCH_NORMAL); + }, + + get URI() { + return this._URI; + }, + + get currentSize() { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + get destination() { + return this._destination; + }, + + get finalURI() { + return this._finalURI; + }, + + get totalSize() { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + /* nsIRequest */ + cancel: function(aStatus) { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + suspend: function() { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + isPending: function() { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + _loadFlags: 0, + get loadFlags() { + return this._loadFlags; + }, + set loadFlags(val) { + this._loadFlags = val; + }, + + _loadGroup: null, + get loadGroup() { + return this._loadGroup; + }, + set loadGroup(val) { + this._loadGroup = val; + }, + + _name: "", + get name() { + return this._name; + }, + + _status: 0, + get status() { + return this._status; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIIncrementalDownload]) +}; + +// Test disconnecting during an update +function run_test_pt1() { + initMockIncrementalDownload(); + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with connection interruption", + Cr.NS_OK, run_test_pt2); +} + +// Test disconnecting during an update +function run_test_pt2() { + gIncrementalDownloadErrorType = 0; + Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_MAXERRORS, 2); + Services.prefs.setIntPref(PREF_APP_UPDATE_RETRYTIMEOUT, 0); + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with connection interruption without recovery", + Cr.NS_ERROR_NET_RESET, run_test_pt3); +} + +// Test entering offline mode while downloading +function run_test_pt3() { + gIncrementalDownloadErrorType = 4; + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); + run_test_helper_pt1("mar download with offline mode", + Cr.NS_OK, finish_test); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js new file mode 100644 index 000000000..ca065f573 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +function run_test() { + setupTestCommon(); + + debugDump("testing resuming an update download in progress for the same " + + "version of the application on startup (Bug 485624)"); + + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_DOWNLOADING); + let updates = getLocalUpdateString(patches, null, null, "1.0", "1.0"); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_DOWNLOADING); + + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + + standardInit(); + + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.activeUpdate.state, STATE_DOWNLOADING, + "the update manager activeUpdate state attribute" + + MSG_SHOULD_EQUAL); + + // Pause the download and reload the Update Manager with an empty update so + // the Application Update Service doesn't write the update xml files during + // xpcom-shutdown which will leave behind the test directory. + gAUS.pauseDownload(); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), true); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + reloadUpdateManagerData(); + + do_execute_soon(doTestFinish); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js new file mode 100644 index 000000000..9715c5828 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const IS_SERVICE_TEST = false; + +/* import-globals-from ../data/xpcshellUtilsAUS.js */ +load("../data/xpcshellUtilsAUS.js"); diff --git a/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js new file mode 100644 index 000000000..831c87257 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js @@ -0,0 +1,285 @@ +/* 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/. + */ + +var gNextRunFunc; +var gExpectedCount; + +function run_test() { + setupTestCommon(); + + debugDump("testing remote update xml attributes"); + + start_httpserver(); + setUpdateURL(gURLData + gHTTPHandlerPath); + setUpdateChannel("test_channel"); + + // This test expects that the app.update.download.backgroundInterval + // preference doesn't already exist. + Services.prefs.deleteBranch("app.update.download.backgroundInterval"); + + standardInit(); + do_execute_soon(run_test_pt01); +} + +// Helper function for testing update counts returned from an update xml +function run_test_helper_pt1(aMsg, aExpectedCount, aNextRunFunc) { + gUpdates = null; + gUpdateCount = null; + gCheckFunc = check_test_helper_pt1; + gNextRunFunc = aNextRunFunc; + gExpectedCount = aExpectedCount; + debugDump(aMsg, Components.stack.caller); + gUpdateChecker.checkForUpdates(updateCheckListener, true); +} + +function check_test_helper_pt1() { + Assert.equal(gUpdateCount, gExpectedCount, + "the update count" + MSG_SHOULD_EQUAL); + gNextRunFunc(); +} + +// update xml not found +function run_test_pt01() { + run_test_helper_pt1("testing update xml not available", + null, run_test_pt02); +} + +// one update available and the update's property values +function run_test_pt02() { + debugDump("testing one update available and the update's property values"); + gUpdates = null; + gUpdateCount = null; + gCheckFunc = check_test_pt02; + let patches = getRemotePatchString("complete", "http://complete/", "SHA1", + "98db9dad8e1d80eda7e1170d0187d6f53e477059", + "9856459"); + patches += getRemotePatchString("partial", "http://partial/", "SHA1", + "e6678ca40ae7582316acdeddf3c133c9c8577de4", + "1316138"); + let updates = getRemoteUpdateString(patches, "minor", "Minor Test", + "version 2.1a1pre", "2.1a1pre", + "20080811053724", + "http://details/", + "true", + "true", "345600", "1200", + "custom1_attr=\"custom1 value\"", + "custom2_attr=\"custom2 value\""); + gResponseBody = getRemoteUpdatesXMLString(updates); + gUpdateChecker.checkForUpdates(updateCheckListener, true); +} + +function check_test_pt02() { + // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244 + // and until this is fixed this will not test the value for detailsURL when it + // isn't specified in the update xml. +// let defaultDetailsURL; +// try { + // Try using a default details URL supplied by the distribution + // if the update XML does not supply one. +// let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. +// getService(Ci.nsIURLFormatter); +// defaultDetailsURL = formatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS); +// } catch (e) { +// defaultDetailsURL = ""; +// } + + Assert.equal(gUpdateCount, 1, + "the update count" + MSG_SHOULD_EQUAL); + let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount).QueryInterface(Ci.nsIPropertyBag); + Assert.equal(bestUpdate.type, "minor", + "the update type attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.name, "Minor Test", + "the update name attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.displayVersion, "version 2.1a1pre", + "the update displayVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.appVersion, "2.1a1pre", + "the update appVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.buildID, "20080811053724", + "the update buildID attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.detailsURL, "http://details/", + "the update detailsURL attribute" + MSG_SHOULD_EQUAL); + Assert.ok(bestUpdate.showPrompt, + "the update showPrompt attribute" + MSG_SHOULD_EQUAL); + Assert.ok(bestUpdate.showNeverForVersion, + "the update showNeverForVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.promptWaitTime, "345600", + "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL); + // The default and maximum value for backgroundInterval is 600 + Assert.equal(bestUpdate.getProperty("backgroundInterval"), "600", + "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.serviceURL, gURLData + gHTTPHandlerPath + "?force=1", + "the update serviceURL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.channel, "test_channel", + "the update channel attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!bestUpdate.isCompleteUpdate, + "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!bestUpdate.isSecurityUpdate, + "the update isSecurityUpdate attribute" + MSG_SHOULD_EQUAL); + // Check that installDate is within 10 seconds of the current date. + Assert.ok((Date.now() - bestUpdate.installDate) < 10000, + "the update installDate attribute should be within 10 seconds of " + + "the current time"); + Assert.ok(!bestUpdate.statusText, + "the update statusText attribute" + MSG_SHOULD_EQUAL); + // nsIUpdate:state returns an empty string when no action has been performed + // on an available update + Assert.equal(bestUpdate.state, "", + "the update state attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.errorCode, 0, + "the update errorCode attribute" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.patchCount, 2, + "the update patchCount attribute" + MSG_SHOULD_EQUAL); + // XXX TODO - test nsIUpdate:serialize + + Assert.equal(bestUpdate.getProperty("custom1_attr"), "custom1 value", + "the update custom1_attr property" + MSG_SHOULD_EQUAL); + Assert.equal(bestUpdate.getProperty("custom2_attr"), "custom2 value", + "the update custom2_attr property" + MSG_SHOULD_EQUAL); + + let patch = bestUpdate.getPatchAt(0); + Assert.equal(patch.type, "complete", + "the update patch type attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.URL, "http://complete/", + "the update patch URL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashFunction, "SHA1", + "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashValue, "98db9dad8e1d80eda7e1170d0187d6f53e477059", + "the update patch hashValue attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.size, 9856459, + "the update patch size attribute" + MSG_SHOULD_EQUAL); + // The value for patch.state can be the string 'null' as a valid value. This + // is confusing if it returns null which is an invalid value since the test + // failure output will show a failure for null == null. To lessen the + // confusion first check that the typeof for patch.state is string. + Assert.equal(typeof patch.state, "string", + "the update patch state typeof value should equal |string|"); + Assert.equal(patch.state, STATE_NONE, + "the update patch state attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!patch.selected, + "the update patch selected attribute" + MSG_SHOULD_EQUAL); + // XXX TODO - test nsIUpdatePatch:serialize + + patch = bestUpdate.getPatchAt(1); + Assert.equal(patch.type, "partial", + "the update patch type attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.URL, "http://partial/", + "the update patch URL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashFunction, "SHA1", + "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashValue, "e6678ca40ae7582316acdeddf3c133c9c8577de4", + "the update patch hashValue attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.size, 1316138, + "the update patch size attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.state, STATE_NONE, + "the update patch state attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!patch.selected, + "the update patch selected attribute" + MSG_SHOULD_EQUAL); + // XXX TODO - test nsIUpdatePatch:serialize + + run_test_pt03(); +} + +// Empty update xml (an empty xml file returns a root node name of parsererror) +function run_test_pt03() { + gResponseBody = ""; + run_test_helper_pt1("testing empty update xml", + null, run_test_pt04); +} + +// no updates available +function run_test_pt04() { + gResponseBody = getRemoteUpdatesXMLString(""); + run_test_helper_pt1("testing no updates available", + 0, run_test_pt05); +} + +// one update available with two patches +function run_test_pt05() { + let patches = getRemotePatchString("complete"); + patches += getRemotePatchString("partial"); + let updates = getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("testing one update available", + 1, run_test_pt06); +} + +// three updates available each with two patches +function run_test_pt06() { + let patches = getRemotePatchString("complete"); + patches += getRemotePatchString("partial"); + let updates = getRemoteUpdateString(patches); + updates += getRemoteUpdateString(patches); + updates += getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("testing three updates available", + 3, run_test_pt07); +} + +// one update with complete and partial patches with size 0 specified in the +// update xml +function run_test_pt07() { + let patches = getRemotePatchString("complete", null, null, null, "0"); + patches += getRemotePatchString("partial", null, null, null, "0"); + let updates = getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("testing one update with complete and partial " + + "patches with size 0", 0, run_test_pt08); +} + +// one update with complete patch with size 0 specified in the update xml +function run_test_pt08() { + let patches = getRemotePatchString("complete", null, null, null, "0"); + let updates = getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("testing one update with complete patch with size 0", + 0, run_test_pt9); +} + +// one update with partial patch with size 0 specified in the update xml +function run_test_pt9() { + let patches = getRemotePatchString("partial", null, null, null, "0"); + let updates = getRemoteUpdateString(patches); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("testing one update with partial patch with size 0", + 0, run_test_pt10); +} + +// check that updates for older versions of the application aren't selected +function run_test_pt10() { + let patches = getRemotePatchString("complete"); + patches += getRemotePatchString("partial"); + let updates = getRemoteUpdateString(patches, "minor", null, null, "1.0pre"); + updates += getRemoteUpdateString(patches, "minor", null, null, "1.0a"); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("testing two updates older than the current version", + 2, check_test_pt10); +} + +function check_test_pt10() { + let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount); + Assert.ok(!bestUpdate, + "there should be no update available"); + run_test_pt11(); +} + +// check that updates for the current version of the application are selected +function run_test_pt11() { + let patches = getRemotePatchString("complete"); + patches += getRemotePatchString("partial"); + let updates = getRemoteUpdateString(patches, "minor", null, "version 1.0"); + gResponseBody = getRemoteUpdatesXMLString(updates); + run_test_helper_pt1("testing one update equal to the current version", + 1, check_test_pt11); +} + +function check_test_pt11() { + let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount); + Assert.ok(!!bestUpdate, + "there should be one update available"); + Assert.equal(bestUpdate.displayVersion, "version 1.0", + "the update displayVersion attribute" + MSG_SHOULD_EQUAL); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js b/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js new file mode 100644 index 000000000..ee1c40bfd --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +Components.utils.import("resource://testing-common/MockRegistrar.jsm"); + +const WindowWatcher = { + openWindow: function(aParent, aUrl, aName, aFeatures, aArgs) { + gCheckFunc(); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]) +}; + +const WindowMediator = { + getMostRecentWindow: function(aWindowType) { + do_execute_soon(check_status); + return { getInterface: XPCOMUtils.generateQI([Ci.nsIDOMWindow]) }; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediator]) +}; + +function run_test() { + setupTestCommon(); + // Calling do_get_profile prevents an error from being logged + do_get_profile(); + + debugDump("testing that an update download doesn't start when the " + + PREF_APP_UPDATE_AUTO + " preference is false"); + + Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, false); + Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false); + + start_httpserver(); + setUpdateURL(gURLData + gHTTPHandlerPath); + standardInit(); + + let windowWatcherCID = + MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", + WindowWatcher); + let windowMediatorCID = + MockRegistrar.register("@mozilla.org/appshell/window-mediator;1", + WindowMediator); + do_register_cleanup(() => { + MockRegistrar.unregister(windowWatcherCID); + MockRegistrar.unregister(windowMediatorCID); + }); + + gCheckFunc = check_showUpdateAvailable; + let patches = getRemotePatchString("complete"); + let updates = getRemoteUpdateString(patches, "minor", null, null, "1.0"); + gResponseBody = getRemoteUpdatesXMLString(updates); + gAUS.notify(null); +} + +function check_status() { + let status = readStatusFile(); + Assert.notEqual(status, STATE_DOWNLOADING, + "the update state" + MSG_SHOULD_EQUAL); + + // Pause the download and reload the Update Manager with an empty update so + // the Application Update Service doesn't write the update xml files during + // xpcom-shutdown which will leave behind the test directory. + gAUS.pauseDownload(); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), true); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + reloadUpdateManagerData(); + + do_execute_soon(doTestFinish); +} + +function check_showUpdateAvailable() { + do_throw("showUpdateAvailable should not have called openWindow!"); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js b/toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js new file mode 100644 index 000000000..25110be8c --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +Components.utils.import("resource://testing-common/MockRegistrar.jsm"); + +/** + * Test that nsIUpdatePrompt doesn't display UI for showUpdateAvailable and + * showUpdateError when the app.update.silent preference is true. + */ + +const WindowWatcher = { + openWindow: function(aParent, aUrl, aName, aFeatures, aArgs) { + gCheckFunc(); + }, + + getNewPrompter: function(aParent) { + gCheckFunc(); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]) +}; + +function run_test() { + setupTestCommon(); + + debugDump("testing nsIUpdatePrompt notifications should not be seen " + + "when the " + PREF_APP_UPDATE_SILENT + " preference is true"); + + Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true); + + let windowWatcherCID = + MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", + WindowWatcher); + do_register_cleanup(() => { + MockRegistrar.unregister(windowWatcherCID); + }); + + standardInit(); + + debugDump("testing showUpdateAvailable should not call openWindow"); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false); + let patches = getLocalPatchString(null, null, null, null, null, null, + STATE_FAILED); + let updates = getLocalUpdateString(patches); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_FAILED); + reloadUpdateManagerData(); + + gCheckFunc = check_showUpdateAvailable; + let update = gUpdateManager.activeUpdate; + gUP.showUpdateAvailable(update); + // Report a successful check after the call to showUpdateAvailable since it + // didn't throw and otherwise it would report no tests run. + Assert.ok(true, + "calling showUpdateAvailable should not attempt to open a window"); + + debugDump("testing showUpdateError should not call getNewPrompter"); + gCheckFunc = check_showUpdateError; + update.errorCode = WRITE_ERROR; + gUP.showUpdateError(update); + // Report a successful check after the call to showUpdateError since it + // didn't throw and otherwise it would report no tests run. + Assert.ok(true, + "calling showUpdateError should not attempt to open a window"); + + doTestFinish(); +} + +function check_showUpdateAvailable() { + do_throw("showUpdateAvailable should not have called openWindow!"); +} + +function check_showUpdateError() { + do_throw("showUpdateError should not have seen getNewPrompter!"); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js b/toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js new file mode 100644 index 000000000..5b694ed30 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +Cu.import("resource://testing-common/MockRegistrar.jsm"); + +const WindowWatcher = { + openWindow: function(aParent, aUrl, aName, aFeatures, aArgs) { + check_showUpdateAvailable(); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]) +}; + +const WindowMediator = { + getMostRecentWindow: function(aWindowType) { + return null; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediator]) +}; + +function run_test() { + setupTestCommon(); + + debugDump("testing nsIUpdatePrompt notifications should not be displayed " + + "when showUpdateAvailable is called for an unsupported system " + + "update when the unsupported notification has already been " + + "shown (bug 843497)"); + + start_httpserver(); + setUpdateURL(gURLData + gHTTPHandlerPath); + standardInit(); + + let windowWatcherCID = + MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", + WindowWatcher); + let windowMediatorCID = + MockRegistrar.register("@mozilla.org/appshell/window-mediator;1", + WindowMediator); + do_register_cleanup(() => { + MockRegistrar.unregister(windowWatcherCID); + MockRegistrar.unregister(windowMediatorCID); + }); + + Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false); + Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true); + // This preference is used to determine when the background update check has + // completed since a successful check will clear the preference. + Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, 1); + + gResponseBody = getRemoteUpdatesXMLString(" \n"); + gAUS.notify(null); + do_execute_soon(check_test); +} + +function check_test() { + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) { + do_execute_soon(check_test); + return; + } + Assert.ok(true, + PREF_APP_UPDATE_BACKGROUNDERRORS + " preference should not exist"); + + stop_httpserver(doTestFinish); +} + +function check_showUpdateAvailable() { + do_throw("showUpdateAvailable should not have called openWindow!"); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js new file mode 100644 index 000000000..e46469455 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js @@ -0,0 +1,177 @@ +/* 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/. + */ + +function run_test() { + setupTestCommon(); + + debugDump("testing addition of a successful update to " + FILE_UPDATES_XML + + " and verification of update properties including the format " + + "prior to bug 530872"); + + setUpdateChannel("test_channel"); + + // This test expects that the app.update.download.backgroundInterval + // preference doesn't already exist. + Services.prefs.deleteBranch("app.update.download.backgroundInterval"); + + // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244 + // and until bug 470244 is fixed this will not test the value for detailsURL + // when it isn't specified in the update xml. + let patches = getLocalPatchString("partial", "http://partial/", "SHA256", + "cd43", "86", "true", STATE_PENDING); + let updates = getLocalUpdateString(patches, "major", "New", "version 4", + "4.0", "20070811053724", + "http://details1/", + "http://service1/", "1238441300314", + "test status text", "false", + "test_channel", "true", "true", "true", + "345600", "300", "3.0", + "custom1_attr=\"custom1 value\"", + "custom2_attr=\"custom2 value\""); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_SUCCEEDED); + + patches = getLocalPatchString("complete", "http://complete/", "SHA1", "6232", + "75", "true", STATE_FAILED); + updates = getLocalUpdateString(patches, "major", "Existing", null, "3.0", + null, + "http://details2/", + "http://service2/", null, + getString("patchApplyFailure"), "true", + "test_channel", "false", null, null, "691200", + null, null, + "custom3_attr=\"custom3 value\"", + "custom4_attr=\"custom4 value\""); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), false); + + standardInit(); + + Assert.ok(!gUpdateManager.activeUpdate, + "the update manager activeUpdate attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 2, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + + debugDump("checking the activeUpdate properties"); + let update = gUpdateManager.getUpdateAt(0).QueryInterface(Ci.nsIPropertyBag); + Assert.equal(update.state, STATE_SUCCEEDED, + "the update state attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.type, "major", + "the update type attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.name, "New", + "the update name attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.displayVersion, "version 4", + "the update displayVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.appVersion, "4.0", + "the update appVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.buildID, "20070811053724", + "the update buildID attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.detailsURL, "http://details1/", + "the update detailsURL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.serviceURL, "http://service1/", + "the update serviceURL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.installDate, "1238441300314", + "the update installDate attribute" + MSG_SHOULD_EQUAL); + // statusText is updated + Assert.equal(update.statusText, getString("installSuccess"), + "the update statusText attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!update.isCompleteUpdate, + "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.channel, "test_channel", + "the update channel attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!!update.showPrompt, + "the update showPrompt attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!!update.showNeverForVersion, + "the update showNeverForVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.promptWaitTime, "345600", + "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.getProperty("backgroundInterval"), "300", + "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.previousAppVersion, "3.0", + "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL); + // Custom attributes + Assert.equal(update.getProperty("custom1_attr"), "custom1 value", + "the update custom1_attr property" + MSG_SHOULD_EQUAL); + Assert.equal(update.getProperty("custom2_attr"), "custom2 value", + "the update custom2_attr property" + MSG_SHOULD_EQUAL); + + debugDump("checking the activeUpdate patch properties"); + let patch = update.selectedPatch; + Assert.equal(patch.type, "partial", + "the update patch type attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.URL, "http://partial/", + "the update patch URL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashFunction, "SHA256", + "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashValue, "cd43", + "the update patch hashValue attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.size, "86", + "the update patch size attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!!patch.selected, + "the update patch selected attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.state, STATE_SUCCEEDED, + "the update patch state attribute" + MSG_SHOULD_EQUAL); + + debugDump("checking the first update properties"); + update = gUpdateManager.getUpdateAt(1).QueryInterface(Ci.nsIPropertyBag); + Assert.equal(update.state, STATE_FAILED, + "the update state attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.name, "Existing", + "the update name attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.type, "major", + "the update type attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.displayVersion, "3.0", + "the update displayVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.appVersion, "3.0", + "the update appVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.detailsURL, "http://details2/", + "the update detailsURL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.serviceURL, "http://service2/", + "the update serviceURL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.installDate, "1238441400314", + "the update installDate attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.statusText, getString("patchApplyFailure"), + "the update statusText attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.buildID, "20080811053724", + "the update buildID attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!!update.isCompleteUpdate, + "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.channel, "test_channel", + "the update channel attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!update.showPrompt, + "the update showPrompt attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!update.showNeverForVersion, + "the update showNeverForVersion attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.promptWaitTime, "691200", + "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL); + // The default and maximum value for backgroundInterval is 600 + Assert.equal(update.getProperty("backgroundInterval"), "600", + "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL); + Assert.equal(update.previousAppVersion, null, + "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL); + // Custom attributes + Assert.equal(update.getProperty("custom3_attr"), "custom3 value", + "the update custom3_attr property" + MSG_SHOULD_EQUAL); + Assert.equal(update.getProperty("custom4_attr"), "custom4 value", + "the update custom4_attr property" + MSG_SHOULD_EQUAL); + + debugDump("checking the first update patch properties"); + patch = update.selectedPatch; + Assert.equal(patch.type, "complete", + "the update patch type attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.URL, "http://complete/", + "the update patch URL attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashFunction, "SHA1", + "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.hashValue, "6232", + "the update patch hashValue attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.size, "75", + "the update patch size attribute" + MSG_SHOULD_EQUAL); + Assert.ok(!!patch.selected, + "the update patch selected attribute" + MSG_SHOULD_EQUAL); + Assert.equal(patch.state, STATE_FAILED, + "the update patch state attribute" + MSG_SHOULD_EQUAL); + + doTestFinish(); +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js new file mode 100644 index 000000000..db7d90094 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js @@ -0,0 +1,305 @@ +/* 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/. + */ + +/* General URL Construction Tests */ + +const URL_PREFIX = URL_HOST + "/"; + +var gAppInfo; + +// Since gUpdateChecker.checkForUpdates uses XMLHttpRequest and XMLHttpRequest +// can be slow it combines the checks whenever possible. +function run_test() { + // This test needs access to omni.ja to read the update.locale file so don't + // use a custom directory for the application directory. + gUseTestAppDir = false; + setupTestCommon(); + + standardInit(); + gAppInfo = Cc["@mozilla.org/xre/app-info;1"]. + getService(Ci.nsIXULAppInfo). + QueryInterface(Ci.nsIXULRuntime); + do_execute_soon(run_test_pt1); +} + + +// url constructed with: +// %PRODUCT% +// %VERSION% +// %BUILD_ID% +// %BUILD_TARGET% +// %LOCALE% +// %CHANNEL% +// %PLATFORM_VERSION% +// %OS_VERSION% +// %SYSTEM_CAPABILITIES% +// %DISTRIBUTION% +// %DISTRIBUTION_VERSION% +function run_test_pt1() { + gCheckFunc = check_test_pt1; + // The code that gets the locale accesses the profile which is only available + // after calling do_get_profile in xpcshell tests. This prevents an error from + // being logged. + do_get_profile(); + + setUpdateChannel("test_channel"); + gDefaultPrefBranch.setCharPref(PREF_DISTRIBUTION_ID, "test_distro"); + gDefaultPrefBranch.setCharPref(PREF_DISTRIBUTION_VERSION, "test_distro_version"); + + let url = URL_PREFIX + "%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/" + + "%LOCALE%/%CHANNEL%/%PLATFORM_VERSION%/%OS_VERSION%/" + + "%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/" + + "updates.xml"; + debugDump("testing url construction - url: " + url); + setUpdateURL(url); + try { + gUpdateChecker.checkForUpdates(updateCheckListener, true); + } catch (e) { + debugDump("The following error is most likely due to a missing " + + "update.locale file"); + do_throw(e); + } +} + +function check_test_pt1() { + let url = URL_PREFIX + gAppInfo.name + "/" + gAppInfo.version + "/" + + gAppInfo.appBuildID + "/" + gAppInfo.OS + "_" + getABI() + "/" + + INSTALL_LOCALE + "/test_channel/" + gAppInfo.platformVersion + "/" + + getOSVersion() + "/" + getSystemCapabilities() + + "/test_distro/test_distro_version/updates.xml?force=1"; + // Log the urls since Assert.equal won't print the entire urls to the log. + if (gRequestURL != url) { + logTestInfo("expected url: " + url); + logTestInfo("returned url: " + gRequestURL); + } + Assert.equal(gRequestURL, url, + "the url" + MSG_SHOULD_EQUAL); + run_test_pt2(); +} + +// url constructed with: +// %CHANNEL% with distribution partners +// %CUSTOM% parameter +// force param when there already is a param - bug 454357 +function run_test_pt2() { + gCheckFunc = check_test_pt2; + let url = URL_PREFIX + "%CHANNEL%/updates.xml?custom=%CUSTOM%"; + debugDump("testing url constructed with %CHANNEL% - " + url); + setUpdateURL(url); + gDefaultPrefBranch.setCharPref(PREFBRANCH_APP_PARTNER + "test_partner1", + "test_partner1"); + gDefaultPrefBranch.setCharPref(PREFBRANCH_APP_PARTNER + "test_partner2", + "test_partner2"); + Services.prefs.setCharPref("app.update.custom", "custom"); + gUpdateChecker.checkForUpdates(updateCheckListener, true); +} + +function check_test_pt2() { + let url = URL_PREFIX + "test_channel-cck-test_partner1-test_partner2/" + + "updates.xml?custom=custom&force=1"; + Assert.equal(gRequestURL, url, + "the url" + MSG_SHOULD_EQUAL); + doTestFinish(); +} + +function getABI() { + let abi; + try { + abi = gAppInfo.XPCOMABI; + } catch (e) { + do_throw("nsIXULAppInfo:XPCOMABI not defined\n"); + } + + if (IS_MACOSX) { + // Mac universal build should report a different ABI than either macppc + // or mactel. This is necessary since nsUpdateService.js will set the ABI to + // Universal-gcc3 for Mac universal builds. + let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. + getService(Ci.nsIMacUtils); + + if (macutils.isUniversalBinary) { + abi += "-u-" + macutils.architecturesInBinary; + } + } else if (IS_WIN) { + // Windows build should report the CPU architecture that it's running on. + abi += "-" + getProcArchitecture(); + } + return abi; +} + +function getOSVersion() { + let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); + let osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); + + if (IS_WIN) { + try { + let servicePack = getServicePack(); + osVersion += "." + servicePack; + } catch (e) { + do_throw("Failure obtaining service pack: " + e); + } + + if ("5.0" === sysInfo.getProperty("version")) { // Win2K + osVersion += " (unknown)"; + } else { + try { + osVersion += " (" + getProcArchitecture() + ")"; + } catch (e) { + do_throw("Failed to obtain processor architecture: " + e); + } + } + } + + if (osVersion) { + try { + osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; + } catch (e) { + // Not all platforms have a secondary widget library, so an error is + // nothing to worry about. + } + osVersion = encodeURIComponent(osVersion); + } + return osVersion; +} + +function getServicePack() { + // NOTE: This function is a helper function and not a test. Thus, + // it uses throw() instead of do_throw(). Any tests that use this function + // should catch exceptions thrown in this function and deal with them + // appropriately (usually by calling do_throw). + const BYTE = ctypes.uint8_t; + const WORD = ctypes.uint16_t; + const DWORD = ctypes.uint32_t; + const WCHAR = ctypes.char16_t; + const BOOL = ctypes.int; + + // This structure is described at: + // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx + const SZCSDVERSIONLENGTH = 128; + const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW', + [ + {dwOSVersionInfoSize: DWORD}, + {dwMajorVersion: DWORD}, + {dwMinorVersion: DWORD}, + {dwBuildNumber: DWORD}, + {dwPlatformId: DWORD}, + {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)}, + {wServicePackMajor: WORD}, + {wServicePackMinor: WORD}, + {wSuiteMask: WORD}, + {wProductType: BYTE}, + {wReserved: BYTE} + ]); + + let kernel32 = ctypes.open("kernel32"); + try { + let GetVersionEx = kernel32.declare("GetVersionExW", + ctypes.default_abi, + BOOL, + OSVERSIONINFOEXW.ptr); + let winVer = OSVERSIONINFOEXW(); + winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size; + + if (0 === GetVersionEx(winVer.address())) { + // Using "throw" instead of "do_throw" (see NOTE above) + throw ("Failure in GetVersionEx (returned 0)"); + } + + return winVer.wServicePackMajor + "." + winVer.wServicePackMinor; + } finally { + kernel32.close(); + } +} + +function getProcArchitecture() { + // NOTE: This function is a helper function and not a test. Thus, + // it uses throw() instead of do_throw(). Any tests that use this function + // should catch exceptions thrown in this function and deal with them + // appropriately (usually by calling do_throw). + const WORD = ctypes.uint16_t; + const DWORD = ctypes.uint32_t; + + // This structure is described at: + // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx + const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO', + [ + {wProcessorArchitecture: WORD}, + {wReserved: WORD}, + {dwPageSize: DWORD}, + {lpMinimumApplicationAddress: ctypes.voidptr_t}, + {lpMaximumApplicationAddress: ctypes.voidptr_t}, + {dwActiveProcessorMask: DWORD.ptr}, + {dwNumberOfProcessors: DWORD}, + {dwProcessorType: DWORD}, + {dwAllocationGranularity: DWORD}, + {wProcessorLevel: WORD}, + {wProcessorRevision: WORD} + ]); + + let kernel32 = ctypes.open("kernel32"); + try { + let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo", + ctypes.default_abi, + ctypes.void_t, + SYSTEM_INFO.ptr); + let sysInfo = SYSTEM_INFO(); + // Default to unknown + sysInfo.wProcessorArchitecture = 0xffff; + + GetNativeSystemInfo(sysInfo.address()); + switch (sysInfo.wProcessorArchitecture) { + case 9: + return "x64"; + case 6: + return "IA64"; + case 0: + return "x86"; + default: + // Using "throw" instead of "do_throw" (see NOTE above) + throw ("Unknown architecture returned from GetNativeSystemInfo: " + sysInfo.wProcessorArchitecture); + } + } finally { + kernel32.close(); + } +} + +/** + * Provides system capability information for application update though it may + * be used by other consumers. + */ +function getSystemCapabilities() { + if (IS_WIN) { + const PF_MMX_INSTRUCTIONS_AVAILABLE = 3; // MMX + const PF_XMMI_INSTRUCTIONS_AVAILABLE = 6; // SSE + const PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10; // SSE2 + const PF_SSE3_INSTRUCTIONS_AVAILABLE = 13; // SSE3 + + let lib = ctypes.open("kernel32.dll"); + let IsProcessorFeaturePresent = lib.declare("IsProcessorFeaturePresent", + ctypes.winapi_abi, + ctypes.int32_t, /* success */ + ctypes.uint32_t); /* DWORD */ + let instructionSet = "unknown"; + try { + if (IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE)) { + instructionSet = "SSE3"; + } else if (IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) { + instructionSet = "SSE2"; + } else if (IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE)) { + instructionSet = "SSE"; + } else if (IsProcessorFeaturePresent(PF_MMX_INSTRUCTIONS_AVAILABLE)) { + instructionSet = "MMX"; + } + } catch (e) { + Cu.reportError("Error getting processor instruction set. " + + "Exception: " + e); + } + + lib.close(); + return instructionSet; + } + + return "NA"; +} diff --git a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini new file mode 100644 index 000000000..0d2205046 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini @@ -0,0 +1,27 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +tags = appupdate +head = head_update.js +tail = + +[canCheckForAndCanApplyUpdates.js] +[urlConstruction.js] +[updateManagerXML.js] +[remoteUpdateXML.js] +[downloadAndHashCheckMar.js] +[cleanupDownloadingForOlderAppVersion.js] +[cleanupDownloadingForDifferentChannel.js] +[cleanupDownloadingForSameVersionAndBuildID.js] +[cleanupDownloadingIncorrectStatus.js] +[cleanupPendingVersionFileIncorrectStatus.js] +[cleanupSuccessLogMove.js] +[cleanupSuccessLogsFIFO.js] +[downloadInterruptedRecovery.js] +[downloadResumeForSameAppVersion.js] +[downloadCompleteAfterPartialFailure.js] +[uiSilentPref.js] +[uiUnsupportedAlreadyNotified.js] +[uiAutoPref.js] diff --git a/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js b/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/mozapps/update/tests/unit_base_updater/head_update.js b/toolkit/mozapps/update/tests/unit_base_updater/head_update.js new file mode 100644 index 000000000..9715c5828 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/head_update.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const IS_SERVICE_TEST = false; + +/* import-globals-from ../data/xpcshellUtilsAUS.js */ +load("../data/xpcshellUtilsAUS.js"); diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js new file mode 100644 index 000000000..f3f767394 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* Callback file not in install directory or a sub-directory of the install + directory failure */ + +const STATE_AFTER_RUNUPDATE = STATE_FAILED_INVALID_CALLBACK_DIR_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = getTestDirFile(FILE_HELPER_BIN).path; + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, null, path); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js new file mode 100644 index 000000000..969f84f9d --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js @@ -0,0 +1,45 @@ +/* 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/. + */ + +/* Too long callback file path failure test */ + +const STATE_AFTER_RUNUPDATE = STATE_FAILED_INVALID_CALLBACK_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = "123456789"; + if (IS_WIN) { + path = "\\" + path; + path = path.repeat(30); // 300 characters + path = "C:" + path; + } else { + path = "/" + path; + path = path.repeat(1000); // 10000 characters + } + + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, null, path); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js new file mode 100644 index 000000000..70e03646a --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js @@ -0,0 +1,47 @@ +/* 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/. + */ + +/* Too long install directory path failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR + : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = "123456789"; + if (IS_WIN) { + path = "\\" + path; + path = path.repeat(30); // 300 characters + path = "C:" + path; + } else { + path = "/" + path; + path = path.repeat(1000); // 10000 characters + } + + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js new file mode 100644 index 000000000..330578de6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js @@ -0,0 +1,44 @@ +/* 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/. + */ + +/* Install directory path traversal failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR + : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = "123456789"; + if (IS_WIN) { + path = "C:\\" + path + "\\..\\" + path; + } else { + path = "/" + path + "/../" + path; + } + + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js new file mode 100644 index 000000000..8ddb34af0 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js @@ -0,0 +1,38 @@ +/* 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/. + */ + +/* Different install and working directories for a regular update failure */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR + : STATE_FAILED_INVALID_APPLYTO_DIR_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = getApplyDirFile("..", false).path; + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js new file mode 100644 index 000000000..c8ae3f0c6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js @@ -0,0 +1,43 @@ +/* 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/. + */ + +/* Patch directory path traversal failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = getUpdatesPatchDir(); + if (IS_WIN) { + path = path + "\\..\\"; + } else { + path = path + "/../"; + } + + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, path, null, null, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js new file mode 100644 index 000000000..e9b227657 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js @@ -0,0 +1,38 @@ +/* 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/. + */ + +/* Different install and working directories for a regular update failure */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR + : STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = getApplyDirFile("..", false).path; + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js new file mode 100644 index 000000000..87bbad4aa --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js @@ -0,0 +1,38 @@ +/* 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/. + */ + +/* Working directory path local UNC failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR + : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = "\\\\.\\" + getApplyDirFile(null, false).path; + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js new file mode 100644 index 000000000..a550909b2 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* Relative working directory path failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR + : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js new file mode 100644 index 000000000..b9f793236 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +const STATE_AFTER_STAGE = STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + createUpdateInProgressLockFile(getAppBaseDir()); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(false); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + removeUpdateInProgressLockFile(getAppBaseDir()); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(PERFORMING_STAGED_UPDATE); + checkUpdateLogContains(ERR_UPDATE_IN_PROGRESS); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js new file mode 100644 index 000000000..a606720b7 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, undefined); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true, false); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + lockDirectory(getAppBaseDir().path); + // Switch the application to the staged application that was updated. + runUpdateUsingApp(STATE_SUCCEEDED); +} + +/** + * Called after the call to runUpdateUsingApp finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + + let updatesDir = getUpdatesPatchDir(); + Assert.ok(updatesDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)); + + let log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js new file mode 100644 index 000000000..00b38adc7 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test a replace request for a staged update with a version file that specifies + * an older version failure. The same check is used in nsUpdateDriver.cpp for + * all update types which is why there aren't tests for the maintenance service + * as well as for other update types. + */ + +const STATE_AFTER_STAGE = STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(false); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Change the active update to an older version to simulate installing a new + // version of the application while there is an update that has been staged. + let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL); + let patches = getLocalPatchString(null, null, null, null, null, "true", + STATE_AFTER_STAGE); + let updates = getLocalUpdateString(patches, null, null, null, "1.0", null, + null, null, null, null, "true", channel); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + // Change the version file to an older version to simulate installing a new + // version of the application while there is an update that has been staged. + writeVersionFile("1.0"); + reloadUpdateManagerData(); + // Try to switch the application to the staged application that was updated. + runUpdateUsingApp(STATE_AFTER_STAGE); +} + +/** + * Called after the call to runUpdateUsingApp finishes. + */ +function runUpdateFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_STAGE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile, IS_MACOSX ? false : true, false); + + let updatesDir = getUpdatesPatchDir(); + Assert.ok(updatesDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)); + + let log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js new file mode 100644 index 000000000..5b9b08156 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, true); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdateUsingApp(STATE_SUCCEEDED); +} + +/** + * Called after the call to runUpdateUsingApp finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + + let updatesDir = getUpdatesPatchDir(); + Assert.ok(updatesDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)); + + let log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js new file mode 100644 index 000000000..e76233fe6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + // The third parameter will test that a full path to the post update binary + // doesn't execute. + setupUpdaterTest(FILE_COMPLETE_MAR, undefined, + getApplyDirFile(null, true).path + "/"); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdateUsingApp(STATE_SUCCEEDED); +} + +/** + * Called after the call to runUpdateUsingApp finishes. + */ +function runUpdateFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + + let updatesDir = getUpdatesPatchDir(); + Assert.ok(updatesDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)); + + let log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js new file mode 100644 index 000000000..b1505d58e --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Application in use complete MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js new file mode 100644 index 000000000..a1cc7d043 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Application in use complete MAR file staged patch apply success test */ + +const START_STATE = STATE_PENDING; +const STATE_AFTER_STAGE = STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestFiles[gTestFiles.length - 1].originalContents = null; + gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n"; + gTestFiles[gTestFiles.length - 1].comparePerms = 0o644; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, true); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + setupSymLinks(); + runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + checkSymLinks(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + checkCallbackLog(); +} + +/** + * Setup symlinks for the test. + */ +function setupSymLinks() { + if (IS_UNIX) { + removeSymlink(); + createSymlink(); + do_register_cleanup(removeSymlink); + gTestFiles.splice(gTestFiles.length - 3, 0, + { + description: "Readable symlink", + fileName: "link", + relPathDir: DIR_RESOURCES, + originalContents: "test", + compareContents: "test", + originalFile: null, + compareFile: null, + originalPerms: 0o666, + comparePerms: 0o666 + }); + } +} + +/** + * Checks the state of the symlinks for the test. + */ +function checkSymLinks() { + if (IS_UNIX) { + checkSymlink(); + } +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js new file mode 100644 index 000000000..93333cade --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Application in use complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js new file mode 100644 index 000000000..79e54c182 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Replace app binary complete MAR file staged patch apply success test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js new file mode 100644 index 000000000..b1f84715f --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Patch app binary partial MAR file staged patch apply success test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js new file mode 100644 index 000000000..85e92d290 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Replace app binary complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js new file mode 100644 index 000000000..1212c9ba2 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Patch app binary partial MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js new file mode 100644 index 000000000..960c96f7b --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js @@ -0,0 +1,47 @@ +/* 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/. + */ + +/* General Partial MAR File Patch Apply Failure Test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[11].originalFile = "partial.png"; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + // If execv is used the updater process will turn into the callback process + // and the updater's return code will be that of the callback process. + runUpdate(STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE, false, (USE_EXECV ? 0 : 1), + true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusFile(), STATE_NONE, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContents(LOG_PARTIAL_FAILURE); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js new file mode 100644 index 000000000..b39595f92 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use complete MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js new file mode 100644 index 000000000..06d386ad6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use partial MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js new file mode 100644 index 000000000..89a2fff5e --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js new file mode 100644 index 000000000..ea85ddccc --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use partial MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js new file mode 100644 index 000000000..c5efaa8c0 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked complete MAR file patch apply failure test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[3]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_PENDING, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_BACKUP_CREATE_7); + checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js new file mode 100644 index 000000000..4fdbadb5b --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked partial MAR file patch apply failure test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[2]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_FAILED_READ_ERROR, false, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_NONE, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_UNABLE_OPEN_DEST); + checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js new file mode 100644 index 000000000..4d12f4e42 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked complete MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[3]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + // Files aren't checked after staging since this test locks a file which + // prevents reading the file. + checkUpdateLogContains(ERR_ENSURE_COPY); + // Switch the application to the staged application that was updated. + runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, false); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_PENDING, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 2, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_BACKUP_CREATE_7); + checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js new file mode 100644 index 000000000..5f64df34c --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked partial MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[2]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + // Files aren't checked after staging since this test locks a file which + // prevents reading the file. + checkUpdateLogContains(ERR_ENSURE_COPY); + // Switch the application to the staged application that was updated. + runUpdate(STATE_FAILED_READ_ERROR, false, 1, false); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_NONE, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 2, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_UNABLE_OPEN_DEST); + checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js new file mode 100644 index 000000000..b83bafccc --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir complete MAR file staged patch apply failure + test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] + + gTestDirs[4].subDirFiles[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js new file mode 100644 index 000000000..39ea485cd --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir partial MAR file staged patch apply failure + test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js new file mode 100644 index 000000000..a71bb8d49 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] + + gTestDirs[4].subDirFiles[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js new file mode 100644 index 000000000..2cbe70ed8 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir partial MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js new file mode 100644 index 000000000..a9ce23420 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js @@ -0,0 +1,41 @@ +/* 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/. + */ + +/* General Partial MAR File Staged Patch Apply Failure Test */ + +const STATE_AFTER_STAGE = STATE_FAILED; +gStagingRemovedUpdate = true; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[11].originalFile = "partial.png"; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js new file mode 100644 index 000000000..f7745f68f --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js @@ -0,0 +1,132 @@ +/* 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/. + */ + +/* General Complete MAR File Staged Patch Apply Test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestFiles[gTestFiles.length - 1].originalContents = null; + gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n"; + gTestFiles[gTestFiles.length - 1].comparePerms = 0o644; + gTestDirs = gTestDirsCompleteSuccess; + setupDistributionDir(); + setupSymLinks(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + checkSymLinks(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are removed when there is a distribution + // directory in the new location. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test1/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory for the test. + */ +function checkDistributionDir() { + if (IS_MACOSX) { + let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + checkUpdateLogContains(REMOVE_OLD_DIST_DIR); + } +} + +/** + * Setup symlinks for the test. + */ +function setupSymLinks() { + // Don't test symlinks on Mac OS X in this test since it tends to timeout. + // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js + if (IS_UNIX && !IS_MACOSX) { + removeSymlink(); + createSymlink(); + do_register_cleanup(removeSymlink); + gTestFiles.splice(gTestFiles.length - 3, 0, + { + description: "Readable symlink", + fileName: "link", + relPathDir: DIR_RESOURCES, + originalContents: "test", + compareContents: "test", + originalFile: null, + compareFile: null, + originalPerms: 0o666, + comparePerms: 0o666 + }); + } +} + +/** + * Checks the state of the symlinks for the test. + */ +function checkSymLinks() { + // Don't test symlinks on Mac OS X in this test since it tends to timeout. + // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js + if (IS_UNIX && !IS_MACOSX) { + checkSymlink(); + } +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js new file mode 100644 index 000000000..ef15326de --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js @@ -0,0 +1,112 @@ +/* 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/. + */ + +/* General Partial MAR File Staged Patch Apply Test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[gTestFiles.length - 2].originalContents = null; + gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n"; + gTestFiles[gTestFiles.length - 2].comparePerms = 0o644; + gTestDirs = gTestDirsPartialSuccess; + preventDistributionFiles(); + setupDistributionDir(); + setupUpdaterTest(FILE_PARTIAL_MAR, true); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true, false, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true, true); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are moved to the new location on update. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory. + */ +function checkDistributionDir() { + let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true); + if (IS_MACOSX) { + Assert.ok(distributionDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(distributionDir.path)); + + let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + + checkUpdateLogContains(MOVE_OLD_DIST_DIR); + } else { + debugDump("testing that files aren't added with an add-if instruction " + + "when the file's destination directory doesn't exist"); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + } +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js new file mode 100644 index 000000000..1008e867f --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js @@ -0,0 +1,96 @@ +/* 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/. + */ + +/* General Complete MAR File Patch Apply Test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + preventDistributionFiles(); + setupDistributionDir(); + setupUpdaterTest(FILE_COMPLETE_MAR, true); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, false, false, true); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are moved to the new location on update. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory. + */ +function checkDistributionDir() { + let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true); + if (IS_MACOSX) { + Assert.ok(distributionDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(distributionDir.path)); + + let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + + checkUpdateLogContains(MOVE_OLD_DIST_DIR); + } else { + debugDump("testing that files aren't added with an add-if instruction " + + "when the file's destination directory doesn't exist"); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + } +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js new file mode 100644 index 000000000..616390f55 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js @@ -0,0 +1,79 @@ +/* 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/. + */ + +/* General Partial MAR File Patch Apply Test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[gTestFiles.length - 1].originalContents = null; + gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n"; + gTestFiles[gTestFiles.length - 1].comparePerms = 0o644; + gTestFiles[gTestFiles.length - 2].originalContents = null; + gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n"; + gTestFiles[gTestFiles.length - 2].comparePerms = 0o644; + gTestDirs = gTestDirsPartialSuccess; + setupDistributionDir(); + // The third parameter will test that a relative path that contains a + // directory traversal to the post update binary doesn't execute. + setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../"); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are removed when there is a distribution + // directory in the new location. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory. + */ +function checkDistributionDir() { + if (IS_MACOSX) { + let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + checkUpdateLogContains(REMOVE_OLD_DIST_DIR); + } +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js b/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js new file mode 100644 index 000000000..86a2eb821 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js @@ -0,0 +1,51 @@ +/* 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/. + */ + +/* Test version downgrade MAR security check */ + +function run_test() { + if (!MOZ_VERIFY_MAR_SIGNATURE) { + return; + } + + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_OLD_VERSION_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + // If execv is used the updater process will turn into the callback process + // and the updater's return code will be that of the callback process. + runUpdate(STATE_FAILED_VERSION_DOWNGRADE_ERROR, false, (USE_EXECV ? 0 : 1), + false); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, VERSION_DOWNGRADE_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(STATE_FAILED_VERSION_DOWNGRADE_ERROR); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js new file mode 100644 index 000000000..6db906fbc --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js @@ -0,0 +1,51 @@ +/* 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/. + */ + +/* Test product/channel MAR security check */ + +function run_test() { + if (!MOZ_VERIFY_MAR_SIGNATURE) { + return; + } + + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_WRONG_CHANNEL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + // If execv is used the updater process will turn into the callback process + // and the updater's return code will be that of the callback process. + runUpdate(STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR, false, (USE_EXECV ? 0 : 1), + false); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, MAR_CHANNEL_MISMATCH_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini new file mode 100644 index 000000000..2b77bee7a --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini @@ -0,0 +1,136 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +; Tests that require the updater binary. These tests should never run on Android +; which doesn't use the updater binary as other applications do and are excluded +; from running the tests in the moz.build file. + +[DEFAULT] +tags = appupdate +head = head_update.js +tail = + +[invalidArgCallbackFileNotInInstallDirFailure.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[invalidArgCallbackFilePathTooLongFailure.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[invalidArgInstallDirPathTooLongFailure.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[invalidArgInstallDirPathTraversalFailure.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[invalidArgInstallWorkingDirPathNotSameFailure_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[invalidArgPatchDirPathTraversalFailure.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[invalidArgStageDirNotInInstallDirFailure_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[invalidArgWorkingDirPathLocalUNCFailure_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[invalidArgWorkingDirPathRelativeFailure.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marSuccessComplete.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marSuccessPartial.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marFailurePartial.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marStageSuccessComplete.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marStageSuccessPartial.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marVersionDowngrade.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 and mar signing +[marWrongChannel.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 and mar signing +[marStageFailurePartial.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marCallbackAppSuccessComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marCallbackAppSuccessPartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marCallbackAppStageSuccessComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marCallbackAppStageSuccessPartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marAppInUseSuccessComplete.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marAppInUseStageSuccessComplete_unix.js] +skip-if = os == 'win' +reason = not a Windows test +[marAppInUseStageFailureComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileLockedFailureComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileLockedFailurePartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileLockedStageFailureComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileLockedStageFailurePartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileInUseSuccessComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileInUseSuccessPartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marRMRFDirFileInUseSuccessComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marRMRFDirFileInUseSuccessPartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileInUseStageFailureComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marFileInUseStageFailurePartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marRMRFDirFileInUseStageFailureComplete_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marRMRFDirFileInUseStageFailurePartial_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marAppApplyDirLockedStageFailure_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marAppApplyUpdateAppBinInUseStageSuccess_win.js] +skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2') +reason = Windows only test and bug 1291985 +[marAppApplyUpdateSuccess.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marAppApplyUpdateStageSuccess.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +[marAppApplyUpdateStageOldVersionFailure.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 diff --git a/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js b/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js new file mode 100644 index 000000000..015fbd0cb --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Bootstrap the tests using the service by installing our own version of the service */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + // We don't actually care if the MAR has any data, we only care about the + // application return code and update.status result. + gTestFiles = gTestFilesCommon; + gTestDirs = []; + setupUpdaterTest(FILE_COMPLETE_MAR, null); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdateUsingService finishes. + */ +function runUpdateFinished() { + checkFilesAfterUpdateSuccess(getApplyDirFile, false, false); + + // We need to check the service log even though this is a bootstrap + // because the app bin could be in use by this test by the time the next + // test runs. + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js new file mode 100644 index 000000000..bf765ee78 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * We skip authenticode cert checks from the service udpates + * so that we can use updater-xpcshell with the wrong certs for testing. + * This tests that code path. */ + +function run_test() { + if (!IS_AUTHENTICODE_CHECK_ENABLED) { + return; + } + + let binDir = getGREBinDir(); + let maintenanceServiceBin = binDir.clone(); + maintenanceServiceBin.append(FILE_MAINTENANCE_SERVICE_BIN); + + let updaterBin = binDir.clone(); + updaterBin.append(FILE_UPDATER_BIN); + + debugDump("Launching maintenance service bin: " + + maintenanceServiceBin.path + " to check updater: " + + updaterBin.path + " signature."); + + // Bypass the manifest and run as invoker + gEnv.set("__COMPAT_LAYER", "RunAsInvoker"); + + let dummyInstallPath = "---"; + let maintenanceServiceBinArgs = ["check-cert", dummyInstallPath, + updaterBin.path]; + let maintenanceServiceBinProcess = Cc["@mozilla.org/process/util;1"]. + createInstance(Ci.nsIProcess); + maintenanceServiceBinProcess.init(maintenanceServiceBin); + maintenanceServiceBinProcess.run(true, maintenanceServiceBinArgs, + maintenanceServiceBinArgs.length); + Assert.equal(maintenanceServiceBinProcess.exitValue, 0, + "the maintenance service exit value should be 0"); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/head_update.js b/toolkit/mozapps/update/tests/unit_service_updater/head_update.js new file mode 100644 index 000000000..38be4ee39 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/head_update.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const IS_SERVICE_TEST = true; + +/* import-globals-from ../data/xpcshellUtilsAUS.js */ +load("../data/xpcshellUtilsAUS.js"); diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js new file mode 100644 index 000000000..70e03646a --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js @@ -0,0 +1,47 @@ +/* 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/. + */ + +/* Too long install directory path failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR + : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = "123456789"; + if (IS_WIN) { + path = "\\" + path; + path = path.repeat(30); // 300 characters + path = "C:" + path; + } else { + path = "/" + path; + path = path.repeat(1000); // 10000 characters + } + + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js new file mode 100644 index 000000000..330578de6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js @@ -0,0 +1,44 @@ +/* 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/. + */ + +/* Install directory path traversal failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR + : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = "123456789"; + if (IS_WIN) { + path = "C:\\" + path + "\\..\\" + path; + } else { + path = "/" + path + "/../" + path; + } + + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js new file mode 100644 index 000000000..8ddb34af0 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js @@ -0,0 +1,38 @@ +/* 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/. + */ + +/* Different install and working directories for a regular update failure */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR + : STATE_FAILED_INVALID_APPLYTO_DIR_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = getApplyDirFile("..", false).path; + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js new file mode 100644 index 000000000..c8ae3f0c6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js @@ -0,0 +1,43 @@ +/* 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/. + */ + +/* Patch directory path traversal failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = getUpdatesPatchDir(); + if (IS_WIN) { + path = path + "\\..\\"; + } else { + path = path + "/../"; + } + + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, path, null, null, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js new file mode 100644 index 000000000..e9b227657 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js @@ -0,0 +1,38 @@ +/* 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/. + */ + +/* Different install and working directories for a regular update failure */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR + : STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = getApplyDirFile("..", false).path; + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js new file mode 100644 index 000000000..87bbad4aa --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js @@ -0,0 +1,38 @@ +/* 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/. + */ + +/* Working directory path local UNC failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR + : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + let path = "\\\\.\\" + getApplyDirFile(null, false).path; + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js new file mode 100644 index 000000000..a550909b2 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* Relative working directory path failure test */ + +const STATE_AFTER_RUNUPDATE = + IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR + : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null); +} + +/** + * Called after the call to runUpdateUsingUpdater finishes. + */ +function runUpdateFinished() { + standardInit(); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js new file mode 100644 index 000000000..b9f793236 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +const STATE_AFTER_STAGE = STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + createUpdateInProgressLockFile(getAppBaseDir()); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(false); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + removeUpdateInProgressLockFile(getAppBaseDir()); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(PERFORMING_STAGED_UPDATE); + checkUpdateLogContains(ERR_UPDATE_IN_PROGRESS); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js new file mode 100644 index 000000000..a606720b7 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, undefined); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true, false); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + lockDirectory(getAppBaseDir().path); + // Switch the application to the staged application that was updated. + runUpdateUsingApp(STATE_SUCCEEDED); +} + +/** + * Called after the call to runUpdateUsingApp finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + + let updatesDir = getUpdatesPatchDir(); + Assert.ok(updatesDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)); + + let log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js new file mode 100644 index 000000000..5b9b08156 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, true); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdateUsingApp(STATE_SUCCEEDED); +} + +/** + * Called after the call to runUpdateUsingApp finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + + let updatesDir = getUpdatesPatchDir(); + Assert.ok(updatesDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)); + + let log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js new file mode 100644 index 000000000..e76233fe6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test applying an update by staging an update and launching an application to + * apply it. + */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + // The third parameter will test that a full path to the post update binary + // doesn't execute. + setupUpdaterTest(FILE_COMPLETE_MAR, undefined, + getApplyDirFile(null, true).path + "/"); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdateUsingApp(STATE_SUCCEEDED); +} + +/** + * Called after the call to runUpdateUsingApp finishes. + */ +function runUpdateFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + + let updatesDir = getUpdatesPatchDir(); + Assert.ok(updatesDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)); + + let log = getUpdateLog(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_LAST_UPDATE_LOG); + Assert.ok(log.exists(), + MSG_SHOULD_EXIST + getMsgPath(log.path)); + + log = getUpdateLog(FILE_BACKUP_UPDATE_LOG); + Assert.ok(!log.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(log.path)); + + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js new file mode 100644 index 000000000..b1505d58e --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Application in use complete MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js new file mode 100644 index 000000000..93333cade --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Application in use complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js new file mode 100644 index 000000000..79e54c182 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Replace app binary complete MAR file staged patch apply success test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js new file mode 100644 index 000000000..b1f84715f --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Patch app binary partial MAR file staged patch apply success test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js new file mode 100644 index 000000000..85e92d290 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Replace app binary complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js new file mode 100644 index 000000000..1212c9ba2 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Patch app binary partial MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + gCallbackBinFile = "exe0.exe"; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js new file mode 100644 index 000000000..960c96f7b --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js @@ -0,0 +1,47 @@ +/* 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/. + */ + +/* General Partial MAR File Patch Apply Failure Test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[11].originalFile = "partial.png"; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + // If execv is used the updater process will turn into the callback process + // and the updater's return code will be that of the callback process. + runUpdate(STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE, false, (USE_EXECV ? 0 : 1), + true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusFile(), STATE_NONE, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContents(LOG_PARTIAL_FAILURE); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js new file mode 100644 index 000000000..b39595f92 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use complete MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js new file mode 100644 index 000000000..06d386ad6 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use partial MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js new file mode 100644 index 000000000..89a2fff5e --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js new file mode 100644 index 000000000..ea85ddccc --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use partial MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName, + false); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js new file mode 100644 index 000000000..c5efaa8c0 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked complete MAR file patch apply failure test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[3]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_PENDING, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_BACKUP_CREATE_7); + checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js new file mode 100644 index 000000000..4fdbadb5b --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked partial MAR file patch apply failure test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[2]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_FAILED_READ_ERROR, false, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_NONE, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_UNABLE_OPEN_DEST); + checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js new file mode 100644 index 000000000..4d12f4e42 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked complete MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[3]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + // Files aren't checked after staging since this test locks a file which + // prevents reading the file. + checkUpdateLogContains(ERR_ENSURE_COPY); + // Switch the application to the staged application that was updated. + runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, false); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_PENDING, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 2, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_BACKUP_CREATE_7); + checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js new file mode 100644 index 000000000..5f64df34c --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File locked partial MAR file staged patch apply failure test */ + +const STATE_AFTER_STAGE = STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperLockFile(gTestFiles[2]); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + // Files aren't checked after staging since this test locks a file which + // prevents reading the file. + checkUpdateLogContains(ERR_ENSURE_COPY); + // Switch the application to the staged application that was updated. + runUpdate(STATE_FAILED_READ_ERROR, false, 1, false); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusFile(), STATE_NONE, + "the status file failure code" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.updateCount, 2, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED, + "the update state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_UNABLE_OPEN_DEST); + checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js new file mode 100644 index 000000000..b83bafccc --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir complete MAR file staged patch apply failure + test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] + + gTestDirs[4].subDirFiles[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js new file mode 100644 index 000000000..39ea485cd --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir partial MAR file staged patch apply failure + test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; +const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + setTestFilesAndDirsForFailure(); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_RENAME_FILE); + checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js new file mode 100644 index 000000000..a71bb8d49 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir complete MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] + + gTestDirs[4].subDirFiles[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js new file mode 100644 index 000000000..2cbe70ed8 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* File in use inside removed dir partial MAR file patch apply success test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestDirs = gTestDirsPartialSuccess; + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true); +} + +/** + * Called after the call to waitForHelperSleep finishes. + */ +function waitForHelperSleepFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + waitForHelperExit(); +} + +/** + * Called after the call to waitForHelperExit finishes. + */ +function waitForHelperExitFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContains(ERR_BACKUP_DISCARD); + checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT); + checkCallbackLog(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js new file mode 100644 index 000000000..a9ce23420 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js @@ -0,0 +1,41 @@ +/* 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/. + */ + +/* General Partial MAR File Staged Patch Apply Failure Test */ + +const STATE_AFTER_STAGE = STATE_FAILED; +gStagingRemovedUpdate = true; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[11].originalFile = "partial.png"; + gTestDirs = gTestDirsPartialSuccess; + setTestFilesAndDirsForFailure(); + setupUpdaterTest(FILE_PARTIAL_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE, + "the update errorCode" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateFailure(getApplyDirFile); + checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED); + waitForFilesInUse(); +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js new file mode 100644 index 000000000..f7745f68f --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js @@ -0,0 +1,132 @@ +/* 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/. + */ + +/* General Complete MAR File Staged Patch Apply Test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestFiles[gTestFiles.length - 1].originalContents = null; + gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n"; + gTestFiles[gTestFiles.length - 1].comparePerms = 0o644; + gTestDirs = gTestDirsCompleteSuccess; + setupDistributionDir(); + setupSymLinks(); + setupUpdaterTest(FILE_COMPLETE_MAR, false); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + checkSymLinks(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are removed when there is a distribution + // directory in the new location. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test1/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory for the test. + */ +function checkDistributionDir() { + if (IS_MACOSX) { + let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + checkUpdateLogContains(REMOVE_OLD_DIST_DIR); + } +} + +/** + * Setup symlinks for the test. + */ +function setupSymLinks() { + // Don't test symlinks on Mac OS X in this test since it tends to timeout. + // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js + if (IS_UNIX && !IS_MACOSX) { + removeSymlink(); + createSymlink(); + do_register_cleanup(removeSymlink); + gTestFiles.splice(gTestFiles.length - 3, 0, + { + description: "Readable symlink", + fileName: "link", + relPathDir: DIR_RESOURCES, + originalContents: "test", + compareContents: "test", + originalFile: null, + compareFile: null, + originalPerms: 0o666, + comparePerms: 0o666 + }); + } +} + +/** + * Checks the state of the symlinks for the test. + */ +function checkSymLinks() { + // Don't test symlinks on Mac OS X in this test since it tends to timeout. + // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js + if (IS_UNIX && !IS_MACOSX) { + checkSymlink(); + } +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js new file mode 100644 index 000000000..ef15326de --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js @@ -0,0 +1,112 @@ +/* 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/. + */ + +/* General Partial MAR File Staged Patch Apply Test */ + +const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED; + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[gTestFiles.length - 2].originalContents = null; + gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n"; + gTestFiles[gTestFiles.length - 2].comparePerms = 0o644; + gTestDirs = gTestDirsPartialSuccess; + preventDistributionFiles(); + setupDistributionDir(); + setupUpdaterTest(FILE_PARTIAL_MAR, true); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + stageUpdate(true); +} + +/** + * Called after the call to stageUpdate finishes. + */ +function stageUpdateFinished() { + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getStageDirFile, true); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true, false, true); + // Switch the application to the staged application that was updated. + runUpdate(STATE_SUCCEEDED, true, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile, false, true); + checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true, true); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are moved to the new location on update. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory. + */ +function checkDistributionDir() { + let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true); + if (IS_MACOSX) { + Assert.ok(distributionDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(distributionDir.path)); + + let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + + checkUpdateLogContains(MOVE_OLD_DIST_DIR); + } else { + debugDump("testing that files aren't added with an add-if instruction " + + "when the file's destination directory doesn't exist"); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + } +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js new file mode 100644 index 000000000..1008e867f --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js @@ -0,0 +1,96 @@ +/* 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/. + */ + +/* General Complete MAR File Patch Apply Test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesCompleteSuccess; + gTestDirs = gTestDirsCompleteSuccess; + preventDistributionFiles(); + setupDistributionDir(); + setupUpdaterTest(FILE_COMPLETE_MAR, true); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkPostUpdateAppLog(); +} + +/** + * Called after the call to checkPostUpdateAppLog finishes. + */ +function checkPostUpdateAppLogFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(true); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_COMPLETE_SUCCESS, false, false, true); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are moved to the new location on update. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory. + */ +function checkDistributionDir() { + let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true); + if (IS_MACOSX) { + Assert.ok(distributionDir.exists(), + MSG_SHOULD_EXIST + getMsgPath(distributionDir.path)); + + let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true); + Assert.ok(testFile.exists(), + MSG_SHOULD_EXIST + getMsgPath(testFile.path)); + + distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + + checkUpdateLogContains(MOVE_OLD_DIST_DIR); + } else { + debugDump("testing that files aren't added with an add-if instruction " + + "when the file's destination directory doesn't exist"); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + } +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js new file mode 100644 index 000000000..616390f55 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js @@ -0,0 +1,79 @@ +/* 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/. + */ + +/* General Partial MAR File Patch Apply Test */ + +function run_test() { + if (!setupTestCommon()) { + return; + } + gTestFiles = gTestFilesPartialSuccess; + gTestFiles[gTestFiles.length - 1].originalContents = null; + gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n"; + gTestFiles[gTestFiles.length - 1].comparePerms = 0o644; + gTestFiles[gTestFiles.length - 2].originalContents = null; + gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n"; + gTestFiles[gTestFiles.length - 2].comparePerms = 0o644; + gTestDirs = gTestDirsPartialSuccess; + setupDistributionDir(); + // The third parameter will test that a relative path that contains a + // directory traversal to the post update binary doesn't execute. + setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../"); +} + +/** + * Called after the call to setupUpdaterTest finishes. + */ +function setupUpdaterTestFinished() { + runUpdate(STATE_SUCCEEDED, false, 0, true); +} + +/** + * Called after the call to runUpdate finishes. + */ +function runUpdateFinished() { + checkAppBundleModTime(); + standardInit(); + Assert.equal(readStatusState(), STATE_NONE, + "the status file state" + MSG_SHOULD_EQUAL); + Assert.ok(!gUpdateManager.activeUpdate, + "the active update should not be defined"); + Assert.equal(gUpdateManager.updateCount, 1, + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL); + Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED, + "the update state" + MSG_SHOULD_EQUAL); + checkPostUpdateRunningFile(false); + checkFilesAfterUpdateSuccess(getApplyDirFile); + checkUpdateLogContents(LOG_PARTIAL_SUCCESS); + checkDistributionDir(); + checkCallbackLog(); +} + +/** + * Setup the state of the distribution directory for the test. + */ +function setupDistributionDir() { + if (IS_MACOSX) { + // Create files in the old distribution directory location to verify that + // the directory and its contents are removed when there is a distribution + // directory in the new location. + let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true); + writeFile(testFile, "test\n"); + testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true); + writeFile(testFile, "test\n"); + } +} + +/** + * Checks the state of the distribution directory. + */ +function checkDistributionDir() { + if (IS_MACOSX) { + let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true); + Assert.ok(!distributionDir.exists(), + MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path)); + checkUpdateLogContains(REMOVE_OLD_DIST_DIR); + } +} diff --git a/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini new file mode 100644 index 000000000..1d63f8583 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini @@ -0,0 +1,156 @@ +; 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/. + +; Tests that require the updater binary and the maintenance service. + +[DEFAULT] +tags = appupdate +head = head_update.js +tail = + +[bootstrapSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[invalidArgInstallDirPathTooLongFailureSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[invalidArgInstallDirPathTraversalFailureSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[invalidArgPatchDirPathTraversalFailureSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[invalidArgStageDirNotInInstallDirFailureSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[invalidArgWorkingDirPathLocalUNCFailureSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[invalidArgWorkingDirPathRelativeFailureSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marSuccessCompleteSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marSuccessPartialSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFailurePartialSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marStageSuccessCompleteSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marStageSuccessPartialSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marStageFailurePartialSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marCallbackAppSuccessCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marCallbackAppSuccessPartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marCallbackAppStageSuccessCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marCallbackAppStageSuccessPartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marAppInUseSuccessCompleteSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marAppInUseStageFailureCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileLockedFailureCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileLockedFailurePartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileLockedStageFailureCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileLockedStageFailurePartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileInUseSuccessCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileInUseSuccessPartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marRMRFDirFileInUseSuccessCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marRMRFDirFileInUseSuccessPartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileInUseStageFailureCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marFileInUseStageFailurePartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marRMRFDirFileInUseStageFailureCompleteSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marRMRFDirFileInUseStageFailurePartialSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marAppApplyDirLockedStageFailureSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marAppApplyUpdateSuccessSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[marAppApplyUpdateStageSuccessSvc.js] +skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') +reason = bug 1291985 +run-sequentially = Uses the Mozilla Maintenance Service. +[checkUpdaterSigSvc.js] diff --git a/toolkit/mozapps/update/updater/Launchd.plist b/toolkit/mozapps/update/updater/Launchd.plist new file mode 100644 index 000000000..f0b5cef08 --- /dev/null +++ b/toolkit/mozapps/update/updater/Launchd.plist @@ -0,0 +1,10 @@ + + + + + Label + org.mozilla.updater + RunAtLoad + + + diff --git a/toolkit/mozapps/update/updater/Makefile.in b/toolkit/mozapps/update/updater/Makefile.in new file mode 100644 index 000000000..84a843d18 --- /dev/null +++ b/toolkit/mozapps/update/updater/Makefile.in @@ -0,0 +1,29 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# 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/. + +# For changes here, also consider ./updater-xpcshell/Makefile.in + +ifndef MOZ_WINCONSOLE +ifdef MOZ_DEBUG +MOZ_WINCONSOLE = 1 +else +MOZ_WINCONSOLE = 0 +endif +endif + +include $(topsrcdir)/config/rules.mk + +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +export:: + sed -e 's/%MOZ_MACBUNDLE_ID%/$(MOZ_MACBUNDLE_ID)/' $(srcdir)/macbuild/Contents/Info.plist.in > $(DIST)/bin/Info.plist +libs:: + $(NSINSTALL) -D $(DIST)/bin/updater.app + rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app + rsync -a -C $(DIST)/bin/Info.plist $(DIST)/bin/updater.app/Contents + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS + $(NSINSTALL) $(DIST)/bin/org.mozilla.updater $(DIST)/bin/updater.app/Contents/MacOS +endif diff --git a/toolkit/mozapps/update/updater/archivereader.cpp b/toolkit/mozapps/update/updater/archivereader.cpp new file mode 100644 index 000000000..90cf45c3d --- /dev/null +++ b/toolkit/mozapps/update/updater/archivereader.cpp @@ -0,0 +1,324 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include "bzlib.h" +#include "archivereader.h" +#include "errors.h" +#ifdef XP_WIN +#include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp +#include "updatehelper.h" +#endif + +// These are generated at compile time based on the DER file for the channel +// being used +#ifdef MOZ_VERIFY_MAR_SIGNATURE +#ifdef TEST_UPDATER +#include "../xpcshellCert.h" +#else +#include "primaryCert.h" +#include "secondaryCert.h" +#endif +#endif + +#define UPDATER_NO_STRING_GLUE_STL +#include "nsVersionComparator.cpp" +#undef UPDATER_NO_STRING_GLUE_STL + +#if defined(XP_UNIX) +# include +#elif defined(XP_WIN) +# include +#endif + +static int inbuf_size = 262144; +static int outbuf_size = 262144; +static char *inbuf = nullptr; +static char *outbuf = nullptr; + +/** + * Performs a verification on the opened MAR file with the passed in + * certificate name ID and type ID. + * + * @param archive The MAR file to verify the signature on. + * @param certData The certificate data. + * @return OK on success, CERT_VERIFY_ERROR on failure. +*/ +template +int +VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE]) +{ + (void)archive; + (void)certData; + +#ifdef MOZ_VERIFY_MAR_SIGNATURE + const uint32_t size = SIZE; + const uint8_t* const data = &certData[0]; + if (mar_verify_signatures(archive, &data, &size, 1)) { + return CERT_VERIFY_ERROR; + } +#endif + + return OK; +} + +/** + * Performs a verification on the opened MAR file. Both the primary and backup + * keys stored are stored in the current process and at least the primary key + * will be tried. Success will be returned as long as one of the two + * signatures verify. + * + * @return OK on success +*/ +int +ArchiveReader::VerifySignature() +{ + if (!mArchive) { + return ARCHIVE_NOT_OPEN; + } + +#ifndef MOZ_VERIFY_MAR_SIGNATURE + return OK; +#else +#ifdef TEST_UPDATER + int rv = VerifyLoadedCert(mArchive, xpcshellCertData); +#else + int rv = VerifyLoadedCert(mArchive, primaryCertData); + if (rv != OK) { + rv = VerifyLoadedCert(mArchive, secondaryCertData); + } +#endif + return rv; +#endif +} + +/** + * Verifies that the MAR file matches the current product, channel, and version + * + * @param MARChannelID The MAR channel name to use, only updates from MARs + * with a matching MAR channel name will succeed. + * If an empty string is passed, no check will be done + * for the channel name in the product information block. + * If a comma separated list of values is passed then + * one value must match. + * @param appVersion The application version to use, only MARs with an + * application version >= to appVersion will be applied. + * @return OK on success + * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block + * could not be read. + * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR + * channel ID doesn't match the MAR + * file's MAR channel ID. + * VERSION_DOWNGRADE_ERROR if the application version for + * this updater is newer than the + * one in the MAR. + */ +int +ArchiveReader::VerifyProductInformation(const char *MARChannelID, + const char *appVersion) +{ + if (!mArchive) { + return ARCHIVE_NOT_OPEN; + } + + ProductInformationBlock productInfoBlock; + int rv = mar_read_product_info_block(mArchive, + &productInfoBlock); + if (rv != OK) { + return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR; + } + + // Only check the MAR channel name if specified, it should be passed in from + // the update-settings.ini file. + if (MARChannelID && strlen(MARChannelID)) { + // Check for at least one match in the comma separated list of values. + const char *delimiter = " ,\t"; + // Make a copy of the string in case a read only memory buffer + // was specified. strtok modifies the input buffer. + char channelCopy[512] = { 0 }; + strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1); + char *channel = strtok(channelCopy, delimiter); + rv = MAR_CHANNEL_MISMATCH_ERROR; + while(channel) { + if (!strcmp(channel, productInfoBlock.MARChannelID)) { + rv = OK; + break; + } + channel = strtok(nullptr, delimiter); + } + } + + if (rv == OK) { + /* Compare both versions to ensure we don't have a downgrade + -1 if appVersion is older than productInfoBlock.productVersion + 1 if appVersion is newer than productInfoBlock.productVersion + 0 if appVersion is the same as productInfoBlock.productVersion + This even works with strings like: + - 12.0a1 being older than 12.0a2 + - 12.0a2 being older than 12.0b1 + - 12.0a1 being older than 12.0 + - 12.0 being older than 12.1a1 */ + int versionCompareResult = + mozilla::CompareVersions(appVersion, productInfoBlock.productVersion); + if (1 == versionCompareResult) { + rv = VERSION_DOWNGRADE_ERROR; + } + } + + free((void *)productInfoBlock.MARChannelID); + free((void *)productInfoBlock.productVersion); + return rv; +} + +int +ArchiveReader::Open(const NS_tchar *path) +{ + if (mArchive) + Close(); + + if (!inbuf) { + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) { + // Try again with a smaller buffer. + inbuf_size = 1024; + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + + if (!outbuf) { + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) { + // Try again with a smaller buffer. + outbuf_size = 1024; + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + +#ifdef XP_WIN + mArchive = mar_wopen(path); +#else + mArchive = mar_open(path); +#endif + if (!mArchive) + return READ_ERROR; + + return OK; +} + +void +ArchiveReader::Close() +{ + if (mArchive) { + mar_close(mArchive); + mArchive = nullptr; + } + + if (inbuf) { + free(inbuf); + inbuf = nullptr; + } + + if (outbuf) { + free(outbuf); + outbuf = nullptr; + } +} + +int +ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + +#ifdef XP_WIN + FILE* fp = _wfopen(dest, L"wb+"); +#else + int fd = creat(dest, item->flags); + if (fd == -1) + return WRITE_ERROR; + + FILE *fp = fdopen(fd, "wb"); +#endif + if (!fp) + return WRITE_ERROR; + + int rv = ExtractItemToStream(item, fp); + + fclose(fp); + return rv; +} + +int +ArchiveReader::ExtractFileToStream(const char *name, FILE *fp) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + + return ExtractItemToStream(item, fp); +} + +int +ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp) +{ + /* decompress the data chunk by chunk */ + + bz_stream strm; + int offset, inlen, outlen, ret = OK; + + memset(&strm, 0, sizeof(strm)); + if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) + return UNEXPECTED_BZIP_ERROR; + + offset = 0; + for (;;) { + if (!item->length) { + ret = UNEXPECTED_MAR_ERROR; + break; + } + + if (offset < (int) item->length && strm.avail_in == 0) { + inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size); + if (inlen <= 0) + return READ_ERROR; + offset += inlen; + strm.next_in = inbuf; + strm.avail_in = inlen; + } + + strm.next_out = outbuf; + strm.avail_out = outbuf_size; + + ret = BZ2_bzDecompress(&strm); + if (ret != BZ_OK && ret != BZ_STREAM_END) { + ret = UNEXPECTED_BZIP_ERROR; + break; + } + + outlen = outbuf_size - strm.avail_out; + if (outlen) { + if (fwrite(outbuf, outlen, 1, fp) != 1) { + ret = WRITE_ERROR_EXTRACT; + break; + } + } + + if (ret == BZ_STREAM_END) { + ret = OK; + break; + } + } + + BZ2_bzDecompressEnd(&strm); + return ret; +} diff --git a/toolkit/mozapps/update/updater/archivereader.h b/toolkit/mozapps/update/updater/archivereader.h new file mode 100644 index 000000000..a9d78aab1 --- /dev/null +++ b/toolkit/mozapps/update/updater/archivereader.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef ArchiveReader_h__ +#define ArchiveReader_h__ + +#include +#include "mar.h" + +#ifdef XP_WIN + typedef WCHAR NS_tchar; +#else + typedef char NS_tchar; +#endif + +// This class provides an API to extract files from an update archive. +class ArchiveReader +{ +public: + ArchiveReader() : mArchive(nullptr) {} + ~ArchiveReader() { Close(); } + + int Open(const NS_tchar *path); + int VerifySignature(); + int VerifyProductInformation(const char *MARChannelID, + const char *appVersion); + void Close(); + + int ExtractFile(const char *item, const NS_tchar *destination); + int ExtractFileToStream(const char *item, FILE *fp); + +private: + int ExtractItemToStream(const MarItem *item, FILE *fp); + + MarFile *mArchive; +}; + +#endif // ArchiveReader_h__ diff --git a/toolkit/mozapps/update/updater/automounter_gonk.cpp b/toolkit/mozapps/update/updater/automounter_gonk.cpp new file mode 100644 index 000000000..3dff2a133 --- /dev/null +++ b/toolkit/mozapps/update/updater/automounter_gonk.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automounter_gonk.h" +#include "updatedefines.h" +#include "updatelogging.h" + +#define LOG_TAG "GonkAutoMounter" + +#define GONK_LOG(level, format, ...) \ + LOG((LOG_TAG ": " format "\n", ##__VA_ARGS__)); \ + __android_log_print(level, LOG_TAG, format, ##__VA_ARGS__) + +#define LOGI(format, ...) GONK_LOG(ANDROID_LOG_INFO, format, ##__VA_ARGS__) +#define LOGE(format, ...) GONK_LOG(ANDROID_LOG_ERROR, format, ##__VA_ARGS__) + +const char *kGonkMountsPath = "/proc/mounts"; +const char *kGonkSystemPath = "/system"; + +GonkAutoMounter::GonkAutoMounter() : mDevice(nullptr), mAccess(Unknown) +{ + if (!RemountSystem(ReadWrite)) { + LOGE("Could not remount %s as read-write.", kGonkSystemPath); + } +} + +GonkAutoMounter::~GonkAutoMounter() +{ + bool result = RemountSystem(ReadOnly); + free(mDevice); + + if (!result) { + // Don't take any chances when remounting as read-only fails, just reboot. + Reboot(); + } +} + +void +GonkAutoMounter::Reboot() +{ + // The android_reboot wrapper provides more safety, doing fancier read-only + // remounting and attempting to sync() the filesystem first. If this fails + // our only hope is to force a reboot directly without these protections. + // For more, see system/core/libcutils/android_reboot.c + LOGE("Could not remount %s as read-only, forcing a system reboot.", + kGonkSystemPath); + LogFlush(); + + if (android_reboot(ANDROID_RB_RESTART, 0, nullptr) != 0) { + LOGE("Safe system reboot failed, attempting to force"); + LogFlush(); + + if (reboot(RB_AUTOBOOT) != 0) { + LOGE("CRITICAL: Failed to force restart"); + } + } +} + +static const char * +MountAccessToString(MountAccess access) +{ + switch (access) { + case ReadOnly: return "read-only"; + case ReadWrite: return "read-write"; + default: return "unknown"; + } +} + +bool +GonkAutoMounter::RemountSystem(MountAccess access) +{ + if (!UpdateMountStatus()) { + return false; + } + + if (mAccess == access) { + return true; + } + + unsigned long flags = MS_REMOUNT; + if (access == ReadOnly) { + flags |= MS_RDONLY; + // Give the system a chance to flush file buffers + sync(); + } + + if (!MountSystem(flags)) { + return false; + } + + // Check status again to verify /system has been properly remounted + if (!UpdateMountStatus()) { + return false; + } + + if (mAccess != access) { + LOGE("Updated mount status %s should be %s", + MountAccessToString(mAccess), + MountAccessToString(access)); + return false; + } + + return true; +} + +bool +GonkAutoMounter::UpdateMountStatus() +{ + FILE *mountsFile = NS_tfopen(kGonkMountsPath, "r"); + + if (mountsFile == nullptr) { + LOGE("Error opening %s: %s", kGonkMountsPath, strerror(errno)); + return false; + } + + // /proc/mounts returns a 0 size from fstat, so we use the same + // pre-allocated buffer size that ADB does here + const int mountsMaxSize = 4096; + char mountData[mountsMaxSize]; + size_t read = fread(mountData, 1, mountsMaxSize - 1, mountsFile); + mountData[read + 1] = '\0'; + + if (ferror(mountsFile)) { + LOGE("Error reading %s, %s", kGonkMountsPath, strerror(errno)); + fclose(mountsFile); + return false; + } + + char *token, *tokenContext; + bool foundSystem = false; + + for (token = strtok_r(mountData, "\n", &tokenContext); + token; + token = strtok_r(nullptr, "\n", &tokenContext)) + { + if (ProcessMount(token)) { + foundSystem = true; + break; + } + } + + fclose(mountsFile); + + if (!foundSystem) { + LOGE("Couldn't find %s mount in %s", kGonkSystemPath, kGonkMountsPath); + } + return foundSystem; +} + +bool +GonkAutoMounter::ProcessMount(const char *mount) +{ + const int strSize = 256; + char mountDev[strSize]; + char mountDir[strSize]; + char mountAccess[strSize]; + + int rv = sscanf(mount, "%255s %255s %*s %255s %*d %*d\n", + mountDev, mountDir, mountAccess); + mountDev[strSize - 1] = '\0'; + mountDir[strSize - 1] = '\0'; + mountAccess[strSize - 1] = '\0'; + + if (rv != 3) { + return false; + } + + if (strcmp(kGonkSystemPath, mountDir) != 0) { + return false; + } + + free(mDevice); + mDevice = strdup(mountDev); + mAccess = Unknown; + + char *option, *optionContext; + for (option = strtok_r(mountAccess, ",", &optionContext); + option; + option = strtok_r(nullptr, ",", &optionContext)) + { + if (strcmp("ro", option) == 0) { + mAccess = ReadOnly; + break; + } else if (strcmp("rw", option) == 0) { + mAccess = ReadWrite; + break; + } + } + + return true; +} + +/* + * Mark the given block device as read-write or read-only, using the BLKROSET + * ioctl. + */ +static void SetBlockReadWriteStatus(const char *blockdev, bool setReadOnly) { + int fd; + int roMode = setReadOnly ? 1 : 0; + + fd = open(blockdev, O_RDONLY); + if (fd < 0) { + return; + } + + if (ioctl(fd, BLKROSET, &roMode) == -1) { + LOGE("Error setting read-only mode on %s to %s: %s", blockdev, + setReadOnly ? "true": "false", strerror(errno)); + } + close(fd); +} + + +bool +GonkAutoMounter::MountSystem(unsigned long flags) +{ + if (!mDevice) { + LOGE("No device was found for %s", kGonkSystemPath); + return false; + } + + // Without setting the block device ro mode to false, we get a permission + // denied error while trying to remount it in read-write. + SetBlockReadWriteStatus(mDevice, (flags & MS_RDONLY)); + + const char *readOnly = flags & MS_RDONLY ? "read-only" : "read-write"; + int result = mount(mDevice, kGonkSystemPath, "none", flags, nullptr); + + if (result != 0) { + LOGE("Error mounting %s as %s: %s", kGonkSystemPath, readOnly, + strerror(errno)); + return false; + } + + LOGI("Mounted %s partition as %s", kGonkSystemPath, readOnly); + return true; +} diff --git a/toolkit/mozapps/update/updater/automounter_gonk.h b/toolkit/mozapps/update/updater/automounter_gonk.h new file mode 100644 index 000000000..e40cacbc2 --- /dev/null +++ b/toolkit/mozapps/update/updater/automounter_gonk.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef AUTOMOUNTER_GONK_H__ +#define AUTOMOUNTER_GONK_H__ + +typedef enum { + ReadOnly, + ReadWrite, + Unknown +} MountAccess; + +/** + * This class will remount the /system partition as read-write in Gonk to allow + * the updater write access. Upon destruction, /system will be remounted back to + * read-only. If something causes /system to remain read-write, this class will + * reboot the device and allow the system to mount as read-only. + * + * Code inspired from AOSP system/core/adb/remount_service.c + */ +class GonkAutoMounter +{ +public: + GonkAutoMounter(); + ~GonkAutoMounter(); + + MountAccess GetAccess() const + { + return mAccess; + } + +private: + bool RemountSystem(MountAccess access); + bool ForceRemountReadOnly(); + bool UpdateMountStatus(); + bool ProcessMount(const char *mount); + bool MountSystem(unsigned long flags); + void Reboot(); + +private: + char *mDevice; + MountAccess mAccess; +}; + +#endif // AUTOMOUNTER_GONK_H__ diff --git a/toolkit/mozapps/update/updater/bspatch.cpp b/toolkit/mozapps/update/updater/bspatch.cpp new file mode 100644 index 000000000..e632fe3d3 --- /dev/null +++ b/toolkit/mozapps/update/updater/bspatch.cpp @@ -0,0 +1,187 @@ +/*- + * Copyright 2003,2004 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Changelog: + * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to + * the header, and make all the types 32-bit. + * --Benjamin Smedberg + */ + +#include "bspatch.h" +#include "errors.h" + +#include +#include +#include +#include +#include +#include + +#if defined(XP_WIN) +# include +#else +# include +#endif + +#ifdef XP_WIN +# include +#else +# include +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +int +MBS_ReadHeader(FILE* file, MBSPatchHeader *header) +{ + size_t s = fread(header, 1, sizeof(MBSPatchHeader), file); + if (s != sizeof(MBSPatchHeader)) + return READ_ERROR; + + header->slen = ntohl(header->slen); + header->scrc32 = ntohl(header->scrc32); + header->dlen = ntohl(header->dlen); + header->cblen = ntohl(header->cblen); + header->difflen = ntohl(header->difflen); + header->extralen = ntohl(header->extralen); + + struct stat hs; + s = fstat(fileno(file), &hs); + if (s) + return READ_ERROR; + + if (memcmp(header->tag, "MBDIFF10", 8) != 0) + return UNEXPECTED_BSPATCH_ERROR; + + if (sizeof(MBSPatchHeader) + + header->cblen + + header->difflen + + header->extralen != uint32_t(hs.st_size)) + return UNEXPECTED_BSPATCH_ERROR; + + return OK; +} + +int +MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile, + unsigned char *fbuffer, FILE* file) +{ + unsigned char *fbufend = fbuffer + header->slen; + + unsigned char *buf = (unsigned char*) malloc(header->cblen + + header->difflen + + header->extralen); + if (!buf) + return BSPATCH_MEM_ERROR; + + int rv = OK; + + size_t r = header->cblen + header->difflen + header->extralen; + unsigned char *wb = buf; + while (r) { + const size_t count = (r > SSIZE_MAX) ? SSIZE_MAX : r; + size_t c = fread(wb, 1, count, patchFile); + if (c != count) { + rv = READ_ERROR; + goto end; + } + + r -= c; + wb += c; + } + + { + MBSPatchTriple *ctrlsrc = (MBSPatchTriple*) buf; + unsigned char *diffsrc = buf + header->cblen; + unsigned char *extrasrc = diffsrc + header->difflen; + + MBSPatchTriple *ctrlend = (MBSPatchTriple*) diffsrc; + unsigned char *diffend = extrasrc; + unsigned char *extraend = extrasrc + header->extralen; + + do { + ctrlsrc->x = ntohl(ctrlsrc->x); + ctrlsrc->y = ntohl(ctrlsrc->y); + ctrlsrc->z = ntohl(ctrlsrc->z); + +#ifdef DEBUG_bsmedberg + printf("Applying block:\n" + " x: %u\n" + " y: %u\n" + " z: %i\n", + ctrlsrc->x, + ctrlsrc->y, + ctrlsrc->z); +#endif + + /* Add x bytes from oldfile to x bytes from the diff block */ + + if (fbuffer + ctrlsrc->x > fbufend || + diffsrc + ctrlsrc->x > diffend) { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + for (uint32_t i = 0; i < ctrlsrc->x; ++i) { + diffsrc[i] += fbuffer[i]; + } + if ((uint32_t) fwrite(diffsrc, 1, ctrlsrc->x, file) != ctrlsrc->x) { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + fbuffer += ctrlsrc->x; + diffsrc += ctrlsrc->x; + + /* Copy y bytes from the extra block */ + + if (extrasrc + ctrlsrc->y > extraend) { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + if ((uint32_t) fwrite(extrasrc, 1, ctrlsrc->y, file) != ctrlsrc->y) { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + extrasrc += ctrlsrc->y; + + /* "seek" forwards in oldfile by z bytes */ + + if (fbuffer + ctrlsrc->z > fbufend) { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + fbuffer += ctrlsrc->z; + + /* and on to the next control block */ + + ++ctrlsrc; + } while (ctrlsrc < ctrlend); + } + +end: + free(buf); + return rv; +} diff --git a/toolkit/mozapps/update/updater/bspatch.h b/toolkit/mozapps/update/updater/bspatch.h new file mode 100644 index 000000000..c24c001cc --- /dev/null +++ b/toolkit/mozapps/update/updater/bspatch.h @@ -0,0 +1,93 @@ +/*- + * Copyright 2003,2004 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Changelog: + * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to + * the header, and make all the types 32-bit. + * --Benjamin Smedberg + */ + +#ifndef bspatch_h__ +#define bspatch_h__ + +#include +#include + +typedef struct MBSPatchHeader_ { + /* "MBDIFF10" */ + char tag[8]; + + /* Length of the file to be patched */ + uint32_t slen; + + /* CRC32 of the file to be patched */ + uint32_t scrc32; + + /* Length of the result file */ + uint32_t dlen; + + /* Length of the control block in bytes */ + uint32_t cblen; + + /* Length of the diff block in bytes */ + uint32_t difflen; + + /* Length of the extra block in bytes */ + uint32_t extralen; + + /* Control block (MBSPatchTriple[]) */ + /* Diff block (binary data) */ + /* Extra block (binary data) */ +} MBSPatchHeader; + +/** + * Read the header of a patch file into the MBSPatchHeader structure. + * + * @param fd Must have been opened for reading, and be at the beginning + * of the file. + */ +int MBS_ReadHeader(FILE* file, MBSPatchHeader *header); + +/** + * Apply a patch. This method does not validate the checksum of the original + * file: client code should validate the checksum before calling this method. + * + * @param patchfd Must have been processed by MBS_ReadHeader + * @param fbuffer The original file read into a memory buffer of length + * header->slen. + * @param filefd Must have been opened for writing. Should be truncated + * to header->dlen if it is an existing file. The offset + * should be at the beginning of the file. + */ +int MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile, + unsigned char *fbuffer, FILE* file); + +typedef struct MBSPatchTriple_ { + uint32_t x; /* add x bytes from oldfile to x bytes from the diff block */ + uint32_t y; /* copy y bytes from the extra block */ + int32_t z; /* seek forwards in oldfile by z bytes */ +} MBSPatchTriple; + +#endif // bspatch_h__ diff --git a/toolkit/mozapps/update/updater/dep1.der b/toolkit/mozapps/update/updater/dep1.der new file mode 100644 index 000000000..95b4ef38c Binary files /dev/null and b/toolkit/mozapps/update/updater/dep1.der differ diff --git a/toolkit/mozapps/update/updater/dep2.der b/toolkit/mozapps/update/updater/dep2.der new file mode 100644 index 000000000..a460d6a16 Binary files /dev/null and b/toolkit/mozapps/update/updater/dep2.der differ diff --git a/toolkit/mozapps/update/updater/gen_cert_header.py b/toolkit/mozapps/update/updater/gen_cert_header.py new file mode 100644 index 000000000..7ecb15619 --- /dev/null +++ b/toolkit/mozapps/update/updater/gen_cert_header.py @@ -0,0 +1,22 @@ +from __future__ import print_function + +import binascii + +def file_byte_generator(filename, block_size = 512): + with open(filename, "rb") as f: + while True: + block = f.read(block_size) + if block: + for byte in block: + yield byte + else: + break + +def create_header(out_fh, in_filename): + assert out_fh.name.endswith('.h') + array_name = out_fh.name[:-2] + 'Data' + hexified = ["0x" + binascii.hexlify(byte) for byte in file_byte_generator(in_filename)] + print("const uint8_t " + array_name + "[] = {", file=out_fh) + print(", ".join(hexified), file=out_fh) + print("};", file=out_fh) + return 0 diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm new file mode 100644 index 000000000..5a36ae623 --- /dev/null +++ b/toolkit/mozapps/update/updater/launchchild_osx.mm @@ -0,0 +1,384 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include +#include +#include +#include "readstrings.h" + +class MacAutoreleasePool { +public: + MacAutoreleasePool() + { + mPool = [[NSAutoreleasePool alloc] init]; + } + ~MacAutoreleasePool() + { + [mPool release]; + } + +private: + NSAutoreleasePool* mPool; +}; + +void LaunchChild(int argc, const char** argv) +{ + MacAutoreleasePool pool; + + @try { + NSString* launchPath = [NSString stringWithUTF8String:argv[0]]; + NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:argc - 1]; + for (int i = 1; i < argc; i++) { + [arguments addObject:[NSString stringWithUTF8String:argv[i]]]; + } + [NSTask launchedTaskWithLaunchPath:launchPath + arguments:arguments]; + } @catch (NSException* e) { + NSLog(@"%@: %@", e.name, e.reason); + } +} + +void +LaunchMacPostProcess(const char* aAppBundle) +{ + MacAutoreleasePool pool; + + // Launch helper to perform post processing for the update; this is the Mac + // analogue of LaunchWinPostProcess (PostUpdateWin). + NSString* iniPath = [NSString stringWithUTF8String:aAppBundle]; + iniPath = + [iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"]; + + NSFileManager* fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:iniPath]) { + // the file does not exist; there is nothing to run + return; + } + + int readResult; + char values[2][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeRelPath\0ExeArg\0", + 2, + values, + "PostUpdateMac"); + if (readResult) { + return; + } + + NSString *exeRelPath = [NSString stringWithUTF8String:values[0]]; + NSString *exeArg = [NSString stringWithUTF8String:values[1]]; + if (!exeArg || !exeRelPath) { + return; + } + + // The path must not traverse directories and it must be a relative path. + if ([exeRelPath rangeOfString:@".."].location != NSNotFound || + [exeRelPath rangeOfString:@"./"].location != NSNotFound || + [exeRelPath rangeOfString:@"/"].location == 0) { + return; + } + + NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle]; + exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath]; + + char optVals[1][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeAsync\0", + 1, + optVals, + "PostUpdateMac"); + + NSTask *task = [[NSTask alloc] init]; + [task setLaunchPath:exeFullPath]; + [task setArguments:[NSArray arrayWithObject:exeArg]]; + [task launch]; + if (!readResult) { + NSString *exeAsync = [NSString stringWithUTF8String:optVals[0]]; + if ([exeAsync isEqualToString:@"false"]) { + [task waitUntilExit]; + } + } + // ignore the return value of the task, there's nothing we can do with it + [task release]; +} + +id ConnectToUpdateServer() +{ + MacAutoreleasePool pool; + + id updateServer = nil; + BOOL isConnected = NO; + int currTry = 0; + const int numRetries = 10; // Number of IPC connection retries before + // giving up. + while (!isConnected && currTry < numRetries) { + @try { + updateServer = (id)[NSConnection + rootProxyForConnectionWithRegisteredName: + @"org.mozilla.updater.server" + host:nil + usingNameServer:[NSSocketPortNameServer sharedInstance]]; + if (!updateServer || + ![updateServer respondsToSelector:@selector(abort)] || + ![updateServer respondsToSelector:@selector(getArguments)] || + ![updateServer respondsToSelector:@selector(shutdown)]) { + NSLog(@"Server doesn't exist or doesn't provide correct selectors."); + sleep(1); // Wait 1 second. + currTry++; + } else { + isConnected = YES; + } + } @catch (NSException* e) { + NSLog(@"Encountered exception, retrying: %@: %@", e.name, e.reason); + sleep(1); // Wait 1 second. + currTry++; + } + } + if (!isConnected) { + NSLog(@"Failed to connect to update server after several retries."); + return nil; + } + return updateServer; +} + +void CleanupElevatedMacUpdate(bool aFailureOccurred) +{ + MacAutoreleasePool pool; + + id updateServer = ConnectToUpdateServer(); + if (updateServer) { + @try { + if (aFailureOccurred) { + [updateServer performSelector:@selector(abort)]; + } else { + [updateServer performSelector:@selector(shutdown)]; + } + } @catch (NSException* e) { } + } + + NSFileManager* manager = [NSFileManager defaultManager]; + [manager removeItemAtPath:@"/Library/PrivilegedHelperTools/org.mozilla.updater" + error:nil]; + [manager removeItemAtPath:@"/Library/LaunchDaemons/org.mozilla.updater.plist" + error:nil]; + const char* launchctlArgs[] = {"/bin/launchctl", + "remove", + "org.mozilla.updater"}; + // The following call will terminate the current process due to the "remove" + // argument in launchctlArgs. + LaunchChild(3, launchctlArgs); +} + +// Note: Caller is responsible for freeing argv. +bool ObtainUpdaterArguments(int* argc, char*** argv) +{ + MacAutoreleasePool pool; + + id updateServer = ConnectToUpdateServer(); + if (!updateServer) { + // Let's try our best and clean up. + CleanupElevatedMacUpdate(true); + return false; // Won't actually get here due to CleanupElevatedMacUpdate. + } + + @try { + NSArray* updaterArguments = + [updateServer performSelector:@selector(getArguments)]; + *argc = [updaterArguments count]; + char** tempArgv = (char**)malloc(sizeof(char*) * (*argc)); + for (int i = 0; i < *argc; i++) { + int argLen = [[updaterArguments objectAtIndex:i] length] + 1; + tempArgv[i] = (char*)malloc(argLen); + strncpy(tempArgv[i], [[updaterArguments objectAtIndex:i] UTF8String], + argLen); + } + *argv = tempArgv; + } @catch (NSException* e) { + // Let's try our best and clean up. + CleanupElevatedMacUpdate(true); + return false; // Won't actually get here due to CleanupElevatedMacUpdate. + } + return true; +} + +/** + * The ElevatedUpdateServer is launched from a non-elevated updater process. + * It allows an elevated updater process (usually a privileged helper tool) to + * connect to it and receive all the necessary arguments to complete a + * successful update. + */ +@interface ElevatedUpdateServer : NSObject +{ + NSArray* mUpdaterArguments; + BOOL mShouldKeepRunning; + BOOL mAborted; +} +- (id)initWithArgs:(NSArray*)args; +- (BOOL)runServer; +- (NSArray*)getArguments; +- (void)abort; +- (BOOL)wasAborted; +- (void)shutdown; +- (BOOL)shouldKeepRunning; +@end + +@implementation ElevatedUpdateServer + +- (id)initWithArgs:(NSArray*)args +{ + self = [super init]; + if (!self) { + return nil; + } + mUpdaterArguments = args; + mShouldKeepRunning = YES; + mAborted = NO; + return self; +} + +- (BOOL)runServer +{ + NSPort* serverPort = [NSSocketPort port]; + NSConnection* server = [NSConnection connectionWithReceivePort:serverPort + sendPort:serverPort]; + [server setRootObject:self]; + if ([server registerName:@"org.mozilla.updater.server" + withNameServer:[NSSocketPortNameServer sharedInstance]] == NO) { + NSLog(@"Unable to register as DirectoryServer."); + NSLog(@"Is another copy running?"); + return NO; + } + + while ([self shouldKeepRunning] && + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]); + return ![self wasAborted]; +} + +- (NSArray*)getArguments +{ + return mUpdaterArguments; +} + +- (void)abort +{ + mAborted = YES; + [self shutdown]; +} + +- (BOOL)wasAborted +{ + return mAborted; +} + +- (void)shutdown +{ + mShouldKeepRunning = NO; +} + +- (BOOL)shouldKeepRunning +{ + return mShouldKeepRunning; +} + +@end + +bool ServeElevatedUpdate(int argc, const char** argv) +{ + MacAutoreleasePool pool; + + NSMutableArray* updaterArguments = [NSMutableArray arrayWithCapacity:argc]; + for (int i = 0; i < argc; i++) { + [updaterArguments addObject:[NSString stringWithUTF8String:argv[i]]]; + } + + ElevatedUpdateServer* updater = + [[ElevatedUpdateServer alloc] initWithArgs:[updaterArguments copy]]; + bool didSucceed = [updater runServer]; + + [updater release]; + return didSucceed; +} + +bool IsOwnedByGroupAdmin(const char* aAppBundle) +{ + MacAutoreleasePool pool; + + NSString* appDir = [NSString stringWithUTF8String:aAppBundle]; + NSFileManager* fileManager = [NSFileManager defaultManager]; + + NSDictionary* attributes = [fileManager attributesOfItemAtPath:appDir + error:nil]; + bool isOwnedByAdmin = false; + if (attributes && + [[attributes valueForKey:NSFileGroupOwnerAccountID] intValue] == 80) { + isOwnedByAdmin = true; + } + return isOwnedByAdmin; +} + +void SetGroupOwnershipAndPermissions(const char* aAppBundle) +{ + MacAutoreleasePool pool; + + NSString* appDir = [NSString stringWithUTF8String:aAppBundle]; + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError* error = nil; + NSArray* paths = + [fileManager subpathsOfDirectoryAtPath:appDir + error:&error]; + if (error) { + return; + } + + // Set group ownership of Firefox.app to 80 ("admin") and permissions to + // 0775. + if (![fileManager setAttributes:@{ NSFileGroupOwnerAccountID: @(80), + NSFilePosixPermissions: @(0775) } + ofItemAtPath:appDir + error:&error] || error) { + return; + } + + NSArray* permKeys = [NSArray arrayWithObjects:NSFileGroupOwnerAccountID, + NSFilePosixPermissions, + nil]; + // For all descendants of Firefox.app, set group ownership to 80 ("admin") and + // ensure write permission for the group. + for (NSString* currPath in paths) { + NSString* child = [appDir stringByAppendingPathComponent:currPath]; + NSDictionary* oldAttributes = + [fileManager attributesOfItemAtPath:child + error:&error]; + if (error) { + return; + } + // Skip symlinks, since they could be pointing to files outside of the .app + // bundle. + if ([oldAttributes fileType] == NSFileTypeSymbolicLink) { + continue; + } + NSNumber* oldPerms = + (NSNumber*)[oldAttributes valueForKey:NSFilePosixPermissions]; + NSArray* permObjects = + [NSArray arrayWithObjects: + [NSNumber numberWithUnsignedLong:80], + [NSNumber numberWithUnsignedLong:[oldPerms shortValue] | 020], + nil]; + NSDictionary* attributes = [NSDictionary dictionaryWithObjects:permObjects + forKeys:permKeys]; + if (![fileManager setAttributes:attributes + ofItemAtPath:child + error:&error] || error) { + return; + } + } +} diff --git a/toolkit/mozapps/update/updater/loaddlls.cpp b/toolkit/mozapps/update/updater/loaddlls.cpp new file mode 100644 index 000000000..b4291a5df --- /dev/null +++ b/toolkit/mozapps/update/updater/loaddlls.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +// Delayed load libraries are loaded when the first symbol is used. +// The following ensures that we load the delayed loaded libraries from the +// system directory. +struct AutoLoadSystemDependencies +{ + AutoLoadSystemDependencies() + { + // Remove the current directory from the search path for dynamically loaded + // DLLs as a precaution. This call has no effect for delay load DLLs. + SetDllDirectory(L""); + + HMODULE module = ::GetModuleHandleW(L"kernel32.dll"); + if (module) { + // SetDefaultDllDirectories is always available on Windows 8 and above. It + // is also available on Windows Vista, Windows Server 2008, and + // Windows 7 when MS KB2533623 has been applied. + decltype(SetDefaultDllDirectories)* setDefaultDllDirectories = + (decltype(SetDefaultDllDirectories)*) GetProcAddress(module, "SetDefaultDllDirectories"); + if (setDefaultDllDirectories) { + setDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); + return; + } + } + + // When SetDefaultDllDirectories is not available, fallback to preloading + // dlls. The order that these are loaded does not matter since they are + // loaded using the LOAD_WITH_ALTERED_SEARCH_PATH flag. +#ifdef HAVE_64BIT_BUILD + // DLLs for Firefox x64 on Windows 7 (x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"sspicli.dll", + L"wsock32.dll" }; + +#else + // DLLs for Firefox x86 on Windows XP through Windows 7 (x86 and x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"crypt32.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"msasn1.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"psapi.dll", + L"secur32.dll", + L"sspicli.dll", + L"userenv.dll", + L"uxtheme.dll", + L"ws2_32.dll", + L"ws2help.dll", + L"wsock32.dll" }; +#endif + + WCHAR systemDirectory[MAX_PATH + 1] = { L'\0' }; + // If GetSystemDirectory fails we accept that we'll load the DLLs from the + // normal search path. + GetSystemDirectoryW(systemDirectory, MAX_PATH + 1); + size_t systemDirLen = wcslen(systemDirectory); + + // Make the system directory path terminate with a slash + if (systemDirectory[systemDirLen - 1] != L'\\' && systemDirLen) { + systemDirectory[systemDirLen] = L'\\'; + ++systemDirLen; + // No need to re-null terminate + } + + // For each known DLL ensure it is loaded from the system32 directory + for (size_t i = 0; i < sizeof(delayDLLs) / sizeof(delayDLLs[0]); ++i) { + size_t fileLen = wcslen(delayDLLs[i]); + wcsncpy(systemDirectory + systemDirLen, delayDLLs[i], + MAX_PATH - systemDirLen); + if (systemDirLen + fileLen <= MAX_PATH) { + systemDirectory[systemDirLen + fileLen] = L'\0'; + } else { + systemDirectory[MAX_PATH] = L'\0'; + } + LPCWSTR fullModulePath = systemDirectory; // just for code readability + // LOAD_WITH_ALTERED_SEARCH_PATH makes a dll look in its own directory for + // dependencies and is only available on Win 7 and below. + LoadLibraryExW(fullModulePath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + } + } +} loadDLLs; diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in new file mode 100644 index 000000000..a9b9fcba9 --- /dev/null +++ b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + org.mozilla.updater + CFBundleIconFile + updater.icns + CFBundleIdentifier + org.mozilla.updater + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + LSMinimumSystemVersion + 10.5 + LSMinimumSystemVersionByArchitecture + + i386 + 10.5.0 + x86_64 + 10.6.0 + + SMAuthorizedClients + + identifier "%MOZ_MACBUNDLE_ID%" and ((anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]) or (anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = "43AQ936H96")) + + + diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo b/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo new file mode 100644 index 000000000..bd04210fb --- /dev/null +++ b/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in new file mode 100644 index 000000000..bca4022e7 --- /dev/null +++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in @@ -0,0 +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/. */ + +/* Localized versions of Info.plist keys */ + +CFBundleName = "%APP_NAME% Software Update"; diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 000000000..6cfb50406 --- /dev/null +++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,19 @@ +{ + IBClasses = ( + { + CLASS = FirstResponder; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; +}, + { + CLASS = UpdaterUI; + LANGUAGE = ObjC; + OUTLETS = { + progressBar = NSProgressIndicator; + progressTextField = NSTextField; + }; + SUPERCLASS = NSObject; +} + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 000000000..150917837 --- /dev/null +++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,22 @@ + + + + + IBDocumentLocation + 111 162 356 240 0 0 1440 878 + IBEditorPositions + + 29 + 106 299 84 44 0 0 1440 878 + + IBFramework Version + 489.0 + IBOpenObjects + + 21 + 29 + + IBSystem Version + 10J567 + + diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib new file mode 100644 index 000000000..61ff02600 Binary files /dev/null and b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns new file mode 100644 index 000000000..d7499c669 Binary files /dev/null and b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns differ diff --git a/toolkit/mozapps/update/updater/module.ver b/toolkit/mozapps/update/updater/module.ver new file mode 100644 index 000000000..771416bb1 --- /dev/null +++ b/toolkit/mozapps/update/updater/module.ver @@ -0,0 +1 @@ +WIN32_MODULE_DESCRIPTION=@MOZ_APP_DISPLAYNAME@ Software Updater diff --git a/toolkit/mozapps/update/updater/moz.build b/toolkit/mozapps/update/updater/moz.build new file mode 100644 index 000000000..1cca83b5b --- /dev/null +++ b/toolkit/mozapps/update/updater/moz.build @@ -0,0 +1,62 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + Program('org.mozilla.updater') +else: + Program('updater') + +updater_rel_path = '' +include('updater-common.build') +if CONFIG['ENABLE_TESTS']: + DIRS += ['updater-xpcshell'] + +CXXFLAGS += CONFIG['MOZ_BZ2_CFLAGS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LDFLAGS += ['-sectcreate', + '__TEXT', + '__info_plist', + TOPOBJDIR + '/dist/bin/Info.plist', + '-sectcreate', + '__TEXT', + '__launchd_plist', + SRCDIR + '/Launchd.plist'] + +GENERATED_FILES = [ + 'primaryCert.h', + 'secondaryCert.h', + 'xpcshellCert.h', +] + +primary_cert = GENERATED_FILES['primaryCert.h'] +secondary_cert = GENERATED_FILES['secondaryCert.h'] + +# This is how the xpcshellCertificate.der file is generated, in case we ever +# have to regenerate it. +# ./certutil -L -d modules/libmar/tests/unit/data -n mycert -r > xpcshellCertificate.der +xpcshell_cert = GENERATED_FILES['xpcshellCert.h'] + +primary_cert.script = 'gen_cert_header.py:create_header' +secondary_cert.script = 'gen_cert_header.py:create_header' +xpcshell_cert.script = 'gen_cert_header.py:create_header' + +if CONFIG['MOZ_UPDATE_CHANNEL'] in ('beta', 'release', 'esr'): + primary_cert.inputs += ['release_primary.der'] + secondary_cert.inputs += ['release_secondary.der'] +elif CONFIG['MOZ_UPDATE_CHANNEL'] in ('nightly', 'aurora', 'nightly-elm', + 'nightly-profiling', 'nightly-oak', + 'nightly-ux'): + primary_cert.inputs += ['nightly_aurora_level3_primary.der'] + secondary_cert.inputs += ['nightly_aurora_level3_secondary.der'] +else: + primary_cert.inputs += ['dep1.der'] + secondary_cert.inputs += ['dep2.der'] + +xpcshell_cert.inputs += ['xpcshellCertificate.der'] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + FINAL_TARGET_FILES.icons += ['updater.png'] diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der new file mode 100644 index 000000000..b22124798 Binary files /dev/null and b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der differ diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der new file mode 100644 index 000000000..2dffbd02d Binary files /dev/null and b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der differ diff --git a/toolkit/mozapps/update/updater/progressui.h b/toolkit/mozapps/update/updater/progressui.h new file mode 100644 index 000000000..5462815de --- /dev/null +++ b/toolkit/mozapps/update/updater/progressui.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef PROGRESSUI_H__ +#define PROGRESSUI_H__ + +#include "updatedefines.h" + +#if defined(XP_WIN) + typedef WCHAR NS_tchar; + #define NS_main wmain +#else + typedef char NS_tchar; + #define NS_main main +#endif + +// Called to perform any initialization of the widget toolkit +int InitProgressUI(int *argc, NS_tchar ***argv); + +#if defined(XP_WIN) + // Called on the main thread at startup + int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true); + int InitProgressUIStrings(); +#elif defined(XP_MACOSX) + // Called on the main thread at startup + int ShowProgressUI(bool indeterminate = false); +#else + // Called on the main thread at startup + int ShowProgressUI(); +#endif +// May be called from any thread +void QuitProgressUI(); + +// May be called from any thread: progress is a number between 0 and 100 +void UpdateProgressUI(float progress); + +#endif // PROGRESSUI_H__ diff --git a/toolkit/mozapps/update/updater/progressui_gonk.cpp b/toolkit/mozapps/update/updater/progressui_gonk.cpp new file mode 100644 index 000000000..f77d0af63 --- /dev/null +++ b/toolkit/mozapps/update/updater/progressui_gonk.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include + +#include + +#include "android/log.h" + +#include "progressui.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoUpdater" , ## args) + +using namespace std; + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + LOG("Starting to apply update ...\n"); + return 0; +} + +void QuitProgressUI() +{ + LOG("Finished applying update\n"); +} + +void UpdateProgressUI(float progress) +{ + assert(0.0f <= progress && progress <= 100.0f); + + static const size_t kProgressBarLength = 50; + static size_t sLastNumBars; + size_t numBars = size_t(float(kProgressBarLength) * progress / 100.0f); + if (numBars == sLastNumBars) { + return; + } + sLastNumBars = numBars; + + size_t numSpaces = kProgressBarLength - numBars; + string bars(numBars, '='); + string spaces(numSpaces, ' '); + LOG("Progress [ %s%s ]\n", bars.c_str(), spaces.c_str()); +} diff --git a/toolkit/mozapps/update/updater/progressui_gtk.cpp b/toolkit/mozapps/update/updater/progressui_gtk.cpp new file mode 100644 index 000000000..902bc5ac8 --- /dev/null +++ b/toolkit/mozapps/update/updater/progressui_gtk.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include "mozilla/Sprintf.h" +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_INTERVAL 100 + +static float sProgressVal; // between 0 and 100 +static gboolean sQuit = FALSE; +static gboolean sEnableUI; +static guint sTimerID; + +static GtkWidget *sWin; +static GtkWidget *sLabel; +static GtkWidget *sProgressBar; + +static const char *sProgramPath; + +static gboolean +UpdateDialog(gpointer data) +{ + if (sQuit) + { + gtk_widget_hide(sWin); + gtk_main_quit(); + } + + float progress = sProgressVal; + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sProgressBar), + progress / 100.0); + + return TRUE; +} + +static gboolean +OnDeleteEvent(GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + return TRUE; +} + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sProgramPath = (*pargv)[0]; + + sEnableUI = gtk_init_check(pargc, pargv); + return 0; +} + +int +ShowProgressUI() +{ + if (!sEnableUI) + return -1; + + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char ini_path[PATH_MAX]; + SprintfLiteral(ini_path, "%s.ini", sProgramPath); + + StringTable strings; + if (ReadStrings(ini_path, &strings) != OK) + return -1; + + sWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (!sWin) + return -1; + + static GdkPixbuf *pixbuf; + char icon_path[PATH_MAX]; + SprintfLiteral(icon_path, "%s.png", sProgramPath); + + g_signal_connect(G_OBJECT(sWin), "delete_event", + G_CALLBACK(OnDeleteEvent), nullptr); + + gtk_window_set_title(GTK_WINDOW(sWin), strings.title); + gtk_window_set_type_hint(GTK_WINDOW(sWin), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_position(GTK_WINDOW(sWin), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_resizable(GTK_WINDOW(sWin), FALSE); + gtk_window_set_decorated(GTK_WINDOW(sWin), TRUE); + gtk_window_set_deletable(GTK_WINDOW(sWin),FALSE); + pixbuf = gdk_pixbuf_new_from_file (icon_path, nullptr); + gtk_window_set_icon(GTK_WINDOW(sWin), pixbuf); + g_object_unref(pixbuf); + + GtkWidget *vbox = gtk_vbox_new(TRUE, 6); + sLabel = gtk_label_new(strings.info); + gtk_misc_set_alignment(GTK_MISC(sLabel), 0.0f, 0.0f); + sProgressBar = gtk_progress_bar_new(); + + gtk_box_pack_start(GTK_BOX(vbox), sLabel, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), sProgressBar, TRUE, TRUE, 0); + + sTimerID = g_timeout_add(TIMER_INTERVAL, UpdateDialog, nullptr); + + gtk_container_set_border_width(GTK_CONTAINER(sWin), 10); + gtk_container_add(GTK_CONTAINER(sWin), vbox); + gtk_widget_show_all(sWin); + + gtk_main(); + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} diff --git a/toolkit/mozapps/update/updater/progressui_null.cpp b/toolkit/mozapps/update/updater/progressui_null.cpp new file mode 100644 index 000000000..cb3ac6369 --- /dev/null +++ b/toolkit/mozapps/update/updater/progressui_null.cpp @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "progressui.h" + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + return 0; +} + +void QuitProgressUI() +{ +} + +void UpdateProgressUI(float progress) +{ +} diff --git a/toolkit/mozapps/update/updater/progressui_osx.mm b/toolkit/mozapps/update/updater/progressui_osx.mm new file mode 100644 index 000000000..54c9c41b7 --- /dev/null +++ b/toolkit/mozapps/update/updater/progressui_osx.mm @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import +#include +#include +#include "mozilla/Sprintf.h" +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_INTERVAL 0.2 + +static float sProgressVal; // between 0 and 100 +static BOOL sQuit = NO; +static BOOL sIndeterminate = NO; +static StringTable sLabels; +static const char *sUpdatePath; + +@interface UpdaterUI : NSObject +{ + IBOutlet NSProgressIndicator *progressBar; + IBOutlet NSTextField *progressTextField; +} +@end + +@implementation UpdaterUI + +-(void)awakeFromNib +{ + NSWindow *w = [progressBar window]; + + [w setTitle:[NSString stringWithUTF8String:sLabels.title]]; + [progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]]; + + NSRect origTextFrame = [progressTextField frame]; + [progressTextField sizeToFit]; + + int widthAdjust = progressTextField.frame.size.width - origTextFrame.size.width; + + if (widthAdjust > 0) { + NSRect f; + f.size.width = w.frame.size.width + widthAdjust; + f.size.height = w.frame.size.height; + [w setFrame:f display:YES]; + } + + [w center]; + + [progressBar setIndeterminate:sIndeterminate]; + [progressBar setDoubleValue:0.0]; + + [[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self + selector:@selector(updateProgressUI:) + userInfo:nil repeats:YES] retain]; + + // Make sure we are on top initially + [NSApp activateIgnoringOtherApps:YES]; +} + +// called when the timer goes off +-(void)updateProgressUI:(NSTimer *)aTimer +{ + if (sQuit) { + [aTimer invalidate]; + [aTimer release]; + + // It seems to be necessary to activate and hide ourselves before we stop, + // otherwise the "run" method will not return until the user focuses some + // other app. The activate step is necessary if we are not the active app. + // This is a big hack, but it seems to do the trick. + [NSApp activateIgnoringOtherApps:YES]; + [NSApp hide:self]; + [NSApp stop:self]; + } + + float progress = sProgressVal; + + [progressBar setDoubleValue:(double)progress]; +} + +// leave this as returning a BOOL instead of NSApplicationTerminateReply +// for backward compatibility +- (BOOL)applicationShouldTerminate:(NSApplication *)sender +{ + return sQuit; +} + +@end + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sUpdatePath = (*pargv)[1]; + + return 0; +} + +int +ShowProgressUI(bool indeterminate) +{ + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char path[PATH_MAX]; + SprintfLiteral(path, "%s/updater.ini", sUpdatePath); + if (ReadStrings(path, &sLabels) != OK) + return -1; + + // Continue the update without showing the Progress UI if any of the supplied + // strings are larger than MAX_TEXT_LEN (Bug 628829). + if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 && + strlen(sLabels.info) < MAX_TEXT_LEN - 1)) + return -1; + + sIndeterminate = indeterminate; + [NSApplication sharedApplication]; + [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; + [NSApp run]; + + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = YES; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} diff --git a/toolkit/mozapps/update/updater/progressui_win.cpp b/toolkit/mozapps/update/updater/progressui_win.cpp new file mode 100644 index 000000000..89bd71e85 --- /dev/null +++ b/toolkit/mozapps/update/updater/progressui_win.cpp @@ -0,0 +1,319 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include + +#include "resource.h" +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_ID 1 +#define TIMER_INTERVAL 100 + +#define RESIZE_WINDOW(hwnd, extrax, extray) \ + { \ + RECT windowSize; \ + GetWindowRect(hwnd, &windowSize); \ + SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \ + windowSize.bottom - windowSize.top + extray, \ + SWP_NOMOVE | SWP_NOZORDER); \ + } + +#define MOVE_WINDOW(hwnd, dx, dy) \ + { \ + RECT rc; \ + POINT pt; \ + GetWindowRect(hwnd, &rc); \ + pt.x = rc.left; \ + pt.y = rc.top; \ + ScreenToClient(GetParent(hwnd), &pt); \ + SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \ + SWP_NOSIZE | SWP_NOZORDER); \ + } + +static float sProgress; // between 0 and 100 +static BOOL sQuit = FALSE; +static BOOL sIndeterminate = FALSE; +static StringTable sUIStrings; + +static BOOL +GetStringsFile(WCHAR filename[MAX_PATH]) +{ + if (!GetModuleFileNameW(nullptr, filename, MAX_PATH)) + return FALSE; + + WCHAR *dot = wcsrchr(filename, '.'); + if (!dot || wcsicmp(dot + 1, L"exe")) + return FALSE; + + wcscpy(dot + 1, L"ini"); + return TRUE; +} + +static void +UpdateDialog(HWND hDlg) +{ + int pos = int(sProgress + 0.5f); + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETPOS, pos, 0L); +} + +// The code in this function is from MSDN: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp +static void +CenterDialog(HWND hDlg) +{ + RECT rc, rcOwner, rcDlg; + + // Get the owner window and dialog box rectangles. + HWND desktop = GetDesktopWindow(); + + GetWindowRect(desktop, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + + // Offset the owner and dialog box rectangles so that + // right and bottom values represent the width and + // height, and then offset the owner again to discard + // space taken up by the dialog box. + + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + + // The new position is the sum of half the remaining + // space and the owner's original position. + + SetWindowPos(hDlg, + HWND_TOP, + rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2), + 0, 0, // ignores size arguments + SWP_NOSIZE); +} + +static void +InitDialog(HWND hDlg) +{ + WCHAR szwTitle[MAX_TEXT_LEN]; + WCHAR szwInfo[MAX_TEXT_LEN]; + + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title, -1, szwTitle, + sizeof(szwTitle)/sizeof(szwTitle[0])); + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info, -1, szwInfo, + sizeof(szwInfo)/sizeof(szwInfo[0])); + + SetWindowTextW(hDlg, szwTitle); + SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo); + + // Set dialog icon + HICON hIcon = LoadIcon(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDI_DIALOG)); + if (hIcon) + SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon); + + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + if (sIndeterminate) { + LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE); + SetWindowLongPtr(hWndPro, GWL_STYLE, val|PBS_MARQUEE); + SendMessage(hWndPro,(UINT) PBM_SETMARQUEE,(WPARAM) TRUE,(LPARAM)50 ); + } + + // Resize the dialog to fit all of the text if necessary. + RECT infoSize, textSize; + HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO); + + // Get the control's font for calculating the new size for the control + HDC hDCInfo = GetDC(hWndInfo); + HFONT hInfoFont, hOldFont = NULL; + hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0); + + if (hInfoFont) + hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont); + + // Measure the space needed for the text on a single line. DT_CALCRECT means + // nothing is drawn. + if (DrawText(hDCInfo, szwInfo, -1, &textSize, + DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) { + GetClientRect(hWndInfo, &infoSize); + SIZE extra; + // Calculate the additional space needed for the text by subtracting from + // the rectangle returned by DrawText the existing client rectangle's width + // and height. + extra.cx = (textSize.right - textSize.left) - \ + (infoSize.right - infoSize.left); + extra.cy = (textSize.bottom - textSize.top) - \ + (infoSize.bottom - infoSize.top); + if (extra.cx < 0) + extra.cx = 0; + if (extra.cy < 0) + extra.cy = 0; + if ((extra.cx > 0) || (extra.cy > 0)) { + RESIZE_WINDOW(hDlg, extra.cx, extra.cy); + RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy); + RESIZE_WINDOW(hWndPro, extra.cx, 0); + MOVE_WINDOW(hWndPro, 0, extra.cy); + } + } + + if (hOldFont) + SelectObject(hDCInfo, hOldFont); + + ReleaseDC(hWndInfo, hDCInfo); + + CenterDialog(hDlg); // make dialog appear in the center of the screen + + SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr); +} + +// Message handler for update dialog. +static LRESULT CALLBACK +DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + InitDialog(hDlg); + return TRUE; + + case WM_TIMER: + if (sQuit) { + EndDialog(hDlg, 0); + } else { + UpdateDialog(hDlg); + } + return TRUE; + + case WM_COMMAND: + return TRUE; + } + return FALSE; +} + +int +InitProgressUI(int *argc, WCHAR ***argv) +{ + return 0; +} + +/** + * Initializes the progress UI strings + * + * @return 0 on success, -1 on error +*/ +int +InitProgressUIStrings() { + // If we do not have updater.ini, then we should not bother showing UI. + WCHAR filename[MAX_PATH]; + if (!GetStringsFile(filename)) { + return -1; + } + + if (_waccess(filename, 04)) { + return -1; + } + + // If the updater.ini doesn't have the required strings, then we should not + // bother showing UI. + if (ReadStrings(filename, &sUIStrings) != OK) { + return -1; + } + + return 0; +} + +int +ShowProgressUI(bool indeterminate, bool initUIStrings) +{ + sIndeterminate = indeterminate; + if (!indeterminate) { + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + Sleep(500); + + if (sQuit || sProgress > 70.0f) + return 0; + } + + // Don't load the UI if there's an .Local directory for redirection. + WCHAR appPath[MAX_PATH + 1] = { L'\0' }; + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) { + return -1; + } + + if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) { + return -1; + } + + wcscat(appPath, L".Local"); + + if (!_waccess(appPath, 04)) { + return -1; + } + + // Don't load the UI if the strings for the UI are not provided. + if (initUIStrings && InitProgressUIStrings() == -1) { + return -1; + } + + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) { + return -1; + } + + // Use an activation context that supports visual styles for the controls. + ACTCTXW actx = {0}; + actx.cbSize = sizeof(ACTCTXW); + actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID; + actx.hModule = GetModuleHandle(NULL); // Use the embedded manifest + // This is needed only for Win XP but doesn't cause a problem with other + // versions of Windows. + actx.lpSource = appPath; + actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST); + + HANDLE hactx = INVALID_HANDLE_VALUE; + hactx = CreateActCtxW(&actx); + ULONG_PTR actxCookie = NULL; + if (hactx != INVALID_HANDLE_VALUE) { + // Push the specified activation context to the top of the activation stack. + ActivateActCtx(hactx, &actxCookie); + } + + INITCOMMONCONTROLSEX icc = { + sizeof(INITCOMMONCONTROLSEX), + ICC_PROGRESS_CLASS + }; + InitCommonControlsEx(&icc); + + DialogBox(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDD_DIALOG), nullptr, + (DLGPROC) DialogProc); + + if (hactx != INVALID_HANDLE_VALUE) { + // Deactivate the context now that the comctl32.dll is loaded. + DeactivateActCtx(0, actxCookie); + } + + return 0; +} + +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +void +UpdateProgressUI(float progress) +{ + sProgress = progress; // 32-bit writes are atomic +} diff --git a/toolkit/mozapps/update/updater/release_primary.der b/toolkit/mozapps/update/updater/release_primary.der new file mode 100644 index 000000000..11417c35e Binary files /dev/null and b/toolkit/mozapps/update/updater/release_primary.der differ diff --git a/toolkit/mozapps/update/updater/release_secondary.der b/toolkit/mozapps/update/updater/release_secondary.der new file mode 100644 index 000000000..16a7ef6d9 Binary files /dev/null and b/toolkit/mozapps/update/updater/release_secondary.der differ diff --git a/toolkit/mozapps/update/updater/resource.h b/toolkit/mozapps/update/updater/resource.h new file mode 100644 index 000000000..3cfa4efda --- /dev/null +++ b/toolkit/mozapps/update/updater/resource.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by updater.rc +// +#define IDD_DIALOG 101 +#define IDC_PROGRESS 1000 +#define IDC_INFO 1002 +#define IDI_DIALOG 1003 +#define TYPE_CERT 512 +#define IDR_PRIMARY_CERT 1004 +#define IDR_BACKUP_CERT 1005 +#define IDS_UPDATER_IDENTITY 1006 +#define IDR_XPCSHELL_CERT 1007 +#define IDR_COMCTL32_MANIFEST 17 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/toolkit/mozapps/update/updater/updater-common.build b/toolkit/mozapps/update/updater/updater-common.build new file mode 100644 index 000000000..02b7338bc --- /dev/null +++ b/toolkit/mozapps/update/updater/updater-common.build @@ -0,0 +1,136 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +srcs = [ + 'archivereader.cpp', + 'bspatch.cpp', + 'updater.cpp', +] + +have_progressui = 0 + +if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'verifymar', + ] + +if CONFIG['OS_ARCH'] == 'WINNT': + have_progressui = 1 + srcs += [ + 'loaddlls.cpp', + 'progressui_win.cpp', + 'win_dirent.cpp', + ] + RCINCLUDE = '%supdater.rc' % updater_rel_path + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + USE_STATIC_LIBS = True + + # Pick up nsWindowsRestart.cpp + LOCAL_INCLUDES += [ + '/toolkit/xre', + ] + USE_LIBS += [ + 'updatecommon-standalone', + ] + OS_LIBS += [ + 'comctl32', + 'ws2_32', + 'shell32', + 'shlwapi', + 'crypt32', + 'advapi32', + ] +elif CONFIG['OS_ARCH'] == 'Linux' and CONFIG['MOZ_VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'nss', + 'signmar', + 'updatecommon', + ] + OS_LIBS += CONFIG['NSPR_LIBS'] +else: + USE_LIBS += [ + 'updatecommon', + ] + +USE_LIBS += [ + 'mar', +] + +if CONFIG['MOZ_SYSTEM_BZ2']: + OS_LIBS += CONFIG['MOZ_BZ2_LIBS'] +else: + USE_LIBS += [ + 'bz2', + ] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + have_progressui = 1 + srcs += [ + 'progressui_gtk.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + have_progressui = 1 + srcs += [ + 'launchchild_osx.mm', + 'progressui_osx.mm', + ] + OS_LIBS += [ + '-framework Cocoa', + '-framework Security', + '-framework SystemConfiguration', + ] + UNIFIED_SOURCES += [ + '/toolkit/xre/updaterfileutils_osx.mm', + ] + LOCAL_INCLUDES += [ + '/toolkit/xre', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + have_progressui = 1 + srcs += [ + 'automounter_gonk.cpp', + 'progressui_gonk.cpp', + ] + DISABLE_STL_WRAPPING = True + OS_LIBS += [ + 'cutils', + 'sysutils', + ] + +if have_progressui == 0: + srcs += [ + 'progressui_null.cpp', + ] + +SOURCES += sorted(srcs) + +DEFINES['NS_NO_XPCOM'] = True +DISABLE_STL_WRAPPING = True +for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'): + DEFINES[var] = '"%s"' % CONFIG[var] + +LOCAL_INCLUDES += [ + '/toolkit/mozapps/update/common', + '/xpcom/glue', +] + +DELAYLOAD_DLLS += [ + 'crypt32.dll', + 'comctl32.dll', + 'userenv.dll', + 'wsock32.dll', +] + +if CONFIG['_MSC_VER']: + WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup'] +elif CONFIG['OS_ARCH'] == 'WINNT': + WIN32_EXE_LDFLAGS += ['-municode'] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + CXXFLAGS += CONFIG['TK_CFLAGS'] + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in new file mode 100644 index 000000000..01822b186 --- /dev/null +++ b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in @@ -0,0 +1,41 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# 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/. + +# For changes here, also consider ../Makefile.in + +XPCSHELLTESTROOT = $(topobjdir)/_tests/xpcshell/toolkit/mozapps/update/tests +MOCHITESTROOT = $(topobjdir)/_tests/testing/mochitest/chrome/toolkit/mozapps/update/tests + +include $(topsrcdir)/config/rules.mk + +ifndef MOZ_WINCONSOLE +ifdef MOZ_DEBUG +MOZ_WINCONSOLE = 1 +else +MOZ_WINCONSOLE = 0 +endif +endif + +ifdef COMPILE_ENVIRONMENT +tools:: +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) + # Copy for xpcshell tests + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS/updater-xpcshell + $(NSINSTALL) updater-xpcshell $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS + rm -Rf $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater-xpcshell.app $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/org.mozilla.updater + + # Copy for mochitest chrome tests + rsync -a -C $(XPCSHELLTESTROOT)/data/updater.app $(MOCHITESTROOT)/data/ +else + cp $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX) + cp $(PROGRAM) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX) +endif +endif # COMPILE_ENVIRONMENT diff --git a/toolkit/mozapps/update/updater/updater-xpcshell/moz.build b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build new file mode 100644 index 000000000..710b7e1de --- /dev/null +++ b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program('updater-xpcshell') + +updater_rel_path = '../' +DIST_INSTALL = False +DEFINES['TEST_UPDATER'] = True +include('../updater-common.build') + +CXXFLAGS += CONFIG['MOZ_BZ2_CFLAGS'] diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp new file mode 100644 index 000000000..63a92c084 --- /dev/null +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -0,0 +1,4454 @@ +/* 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/. */ + +/** + * Manifest Format + * --------------- + * + * contents = 1*( line ) + * line = method LWS *( param LWS ) CRLF + * CRLF = "\r\n" + * LWS = 1*( " " | "\t" ) + * + * Available methods for the manifest file: + * + * updatev2.manifest + * ----------------- + * method = "add" | "add-if" | "patch" | "patch-if" | "remove" | + * "rmdir" | "rmrfdir" | type + * + * 'type' is the update type (e.g. complete or partial) and when present MUST + * be the first entry in the update manifest. The type is used to support + * downgrades by causing the actions defined in precomplete to be performed. + * + * updatev3.manifest + * ----------------- + * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" | + * "remove" | "rmdir" | "rmrfdir" | type + * + * 'add-if-not' adds a file if it doesn't exist. + * + * precomplete + * ----------- + * method = "remove" | "rmdir" + */ +#include "bspatch.h" +#include "progressui.h" +#include "archivereader.h" +#include "readstrings.h" +#include "errors.h" +#include "bzlib.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "updatecommon.h" +#ifdef XP_MACOSX +#include "updaterfileutils_osx.h" +#endif // XP_MACOSX + +#include "mozilla/Compiler.h" +#include "mozilla/Types.h" +#include "mozilla/UniquePtr.h" + +// Amount of the progress bar to use in each of the 3 update stages, +// should total 100.0. +#define PROGRESS_PREPARE_SIZE 20.0f +#define PROGRESS_EXECUTE_SIZE 75.0f +#define PROGRESS_FINISH_SIZE 5.0f + +// Amount of time in ms to wait for the parent process to close +#ifdef DEBUG +// Use a large value for debug builds since the xpcshell tests take a long time. +#define PARENT_WAIT 30000 +#else +#define PARENT_WAIT 10000 +#endif + +#if defined(XP_MACOSX) +// These functions are defined in launchchild_osx.mm +void CleanupElevatedMacUpdate(bool aFailureOccurred); +bool IsOwnedByGroupAdmin(const char* aAppBundle); +bool IsRecursivelyWritable(const char* aPath); +void LaunchChild(int argc, const char** argv); +void LaunchMacPostProcess(const char* aAppBundle); +bool ObtainUpdaterArguments(int* argc, char*** argv); +bool ServeElevatedUpdate(int argc, const char** argv); +void SetGroupOwnershipAndPermissions(const char* aAppBundle); +struct UpdateServerThreadArgs +{ + int argc; + const NS_tchar** argv; +}; +#endif + +#ifndef _O_BINARY +# define _O_BINARY 0 +#endif + +#ifndef NULL +# define NULL (0) +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +// We want to use execv to invoke the callback executable on platforms where +// we were launched using execv. See nsUpdateDriver.cpp. +#if defined(XP_UNIX) && !defined(XP_MACOSX) +#define USE_EXECV +#endif + +#if defined(MOZ_WIDGET_GONK) +# include "automounter_gonk.h" +# include +# include +# include +# include + +#if ANDROID_VERSION < 21 +// The only header file in bionic which has a function prototype for ioprio_set +// is libc/include/sys/linux-unistd.h. However, linux-unistd.h conflicts +// badly with unistd.h, so we declare the prototype for ioprio_set directly. +extern "C" MOZ_EXPORT int ioprio_set(int which, int who, int ioprio); +#else +# include +static int ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} +#endif + +# define MAYBE_USE_HARD_LINKS 1 +static bool sUseHardLinks = true; +#else +# define MAYBE_USE_HARD_LINKS 0 +#endif + +#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \ + !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) +#include "nss.h" +#include "prerror.h" +#endif + +#ifdef XP_WIN +#ifdef MOZ_MAINTENANCE_SERVICE +#include "registrycertificates.h" +#endif +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, + LPCWSTR siblingFilePath, + LPCWSTR newFileName); +#include "updatehelper.h" + +// Closes the handle if valid and if the updater is elevated returns with the +// return code specified. This prevents multiple launches of the callback +// application by preventing the elevated process from launching the callback. +#define EXIT_WHEN_ELEVATED(path, handle, retCode) \ + { \ + if (handle != INVALID_HANDLE_VALUE) { \ + CloseHandle(handle); \ + } \ + if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \ + LogFinish(); \ + return retCode; \ + } \ + } +#endif + +//----------------------------------------------------------------------------- + +// This variable lives in libbz2. It's declared in bzlib_private.h, so we just +// declare it here to avoid including that entire header file. +#define BZ2_CRC32TABLE_UNDECLARED + +#if MOZ_IS_GCC || defined(__clang__) +extern "C" __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256]; +#undef BZ2_CRC32TABLE_UNDECLARED +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +extern "C" __global unsigned int BZ2_crc32Table[256]; +#undef BZ2_CRC32TABLE_UNDECLARED +#endif +#if defined(BZ2_CRC32TABLE_UNDECLARED) +extern "C" unsigned int BZ2_crc32Table[256]; +#undef BZ2_CRC32TABLE_UNDECLARED +#endif + +static unsigned int +crc32(const unsigned char *buf, unsigned int len) +{ + unsigned int crc = 0xffffffffL; + + const unsigned char *end = buf + len; + for (; buf != end; ++buf) + crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf]; + + crc = ~crc; + return crc; +} + +//----------------------------------------------------------------------------- + +// A simple stack based container for a FILE struct that closes the +// file descriptor from its destructor. +class AutoFile +{ +public: + explicit AutoFile(FILE* file = nullptr) + : mFile(file) { + } + + ~AutoFile() { + if (mFile != nullptr) + fclose(mFile); + } + + AutoFile &operator=(FILE* file) { + if (mFile != 0) + fclose(mFile); + mFile = file; + return *this; + } + + operator FILE*() { + return mFile; + } + + FILE* get() { + return mFile; + } + +private: + FILE* mFile; +}; + +struct MARChannelStringTable { + MARChannelStringTable() + { + MARChannelID[0] = '\0'; + } + + char MARChannelID[MAX_TEXT_LEN]; +}; + +//----------------------------------------------------------------------------- + +typedef void (* ThreadFunc)(void *param); + +#ifdef XP_WIN +#include + +class Thread +{ +public: + int Run(ThreadFunc func, void *param) + { + mThreadFunc = func; + mThreadParam = param; + + unsigned int threadID; + + mThread = (HANDLE) _beginthreadex(nullptr, 0, ThreadMain, this, 0, + &threadID); + + return mThread ? 0 : -1; + } + int Join() + { + WaitForSingleObject(mThread, INFINITE); + CloseHandle(mThread); + return 0; + } +private: + static unsigned __stdcall ThreadMain(void *p) + { + Thread *self = (Thread *) p; + self->mThreadFunc(self->mThreadParam); + return 0; + } + HANDLE mThread; + ThreadFunc mThreadFunc; + void *mThreadParam; +}; + +#elif defined(XP_UNIX) +#include + +class Thread +{ +public: + int Run(ThreadFunc func, void *param) + { + return pthread_create(&thr, nullptr, (void* (*)(void *)) func, param); + } + int Join() + { + void *result; + return pthread_join(thr, &result); + } +private: + pthread_t thr; +}; + +#else +#error "Unsupported platform" +#endif + +//----------------------------------------------------------------------------- + +static NS_tchar gPatchDirPath[MAXPATHLEN]; +static NS_tchar gInstallDirPath[MAXPATHLEN]; +static NS_tchar gWorkingDirPath[MAXPATHLEN]; +static ArchiveReader gArchiveReader; +static bool gSucceeded = false; +static bool sStagedUpdate = false; +static bool sReplaceRequest = false; +static bool sUsingService = false; +static bool sIsOSUpdate = false; + +#ifdef XP_WIN +// The current working directory specified in the command line. +static NS_tchar* gDestPath; +static NS_tchar gCallbackRelPath[MAXPATHLEN]; +static NS_tchar gCallbackBackupPath[MAXPATHLEN]; +static NS_tchar gDeleteDirPath[MAXPATHLEN]; +#endif + +static const NS_tchar kWhitespace[] = NS_T(" \t"); +static const NS_tchar kNL[] = NS_T("\r\n"); +static const NS_tchar kQuote[] = NS_T("\""); + +static inline size_t +mmin(size_t a, size_t b) +{ + return (a > b) ? b : a; +} + +static NS_tchar* +mstrtok(const NS_tchar *delims, NS_tchar **str) +{ + if (!*str || !**str) { + *str = nullptr; + return nullptr; + } + + // skip leading "whitespace" + NS_tchar *ret = *str; + const NS_tchar *d; + do { + for (d = delims; *d != NS_T('\0'); ++d) { + if (*ret == *d) { + ++ret; + break; + } + } + } while (*d); + + if (!*ret) { + *str = ret; + return nullptr; + } + + NS_tchar *i = ret; + do { + for (d = delims; *d != NS_T('\0'); ++d) { + if (*i == *d) { + *i = NS_T('\0'); + *str = ++i; + return ret; + } + } + ++i; + } while (*i); + + *str = nullptr; + return ret; +} + +static bool +EnvHasValue(const char *name) +{ + const char *val = getenv(name); + return (val && *val); +} + +/** + * Coverts a relative update path to a full path. + * + * @param relpath + * The relative path to convert to a full path. + * @return valid filesystem full path or nullptr if memory allocation fails. + */ +static NS_tchar* +get_full_path(const NS_tchar *relpath) +{ + NS_tchar *destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath; + size_t lendestpath = NS_tstrlen(destpath); + size_t lenrelpath = NS_tstrlen(relpath); + NS_tchar *s = new NS_tchar[lendestpath + lenrelpath + 2]; + if (!s) { + return nullptr; + } + + NS_tchar *c = s; + + NS_tstrcpy(c, destpath); + c += lendestpath; + NS_tstrcat(c, NS_T("/")); + c++; + + NS_tstrcat(c, relpath); + c += lenrelpath; + *c = NS_T('\0'); + return s; +} + +/** + * Converts a full update path into a relative path; reverses get_full_path. + * + * @param fullpath + * The absolute path to convert into a relative path. + * return pointer to the location within fullpath where the relative path starts + * or fullpath itself if it already looks relative. + */ +static const NS_tchar* +get_relative_path(const NS_tchar *fullpath) +{ + // If the path isn't absolute, just return it as-is. +#ifdef XP_WIN + if (fullpath[1] != ':' && fullpath[2] != '\\') { +#else + if (fullpath[0] != '/') { +#endif + return fullpath; + } + + NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath; + + // If the path isn't long enough to be absolute, return it as-is. + if (NS_tstrlen(fullpath) <= NS_tstrlen(prefix)) { + return fullpath; + } + + return fullpath + NS_tstrlen(prefix) + 1; +} + +/** + * Gets the platform specific path and performs simple checks to the path. If + * the path checks don't pass nullptr will be returned. + * + * @param line + * The line from the manifest that contains the path. + * @param isdir + * Whether the path is a directory path. Defaults to false. + * @return valid filesystem path or nullptr if the path checks fail. + */ +static NS_tchar* +get_valid_path(NS_tchar **line, bool isdir = false) +{ + NS_tchar *path = mstrtok(kQuote, line); + if (!path) { + LOG(("get_valid_path: unable to determine path: " LOG_S, line)); + return nullptr; + } + + // All paths must be relative from the current working directory + if (path[0] == NS_T('/')) { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } + +#ifdef XP_WIN + // All paths must be relative from the current working directory + if (path[0] == NS_T('\\') || path[1] == NS_T(':')) { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } +#endif + + if (isdir) { + // Directory paths must have a trailing forward slash. + if (path[NS_tstrlen(path) - 1] != NS_T('/')) { + LOG(("get_valid_path: directory paths must have a trailing forward " \ + "slash: " LOG_S, path)); + return nullptr; + } + + // Remove the trailing forward slash because stat on Windows will return + // ENOENT if the path has a trailing slash. + path[NS_tstrlen(path) - 1] = NS_T('\0'); + } + + // Don't allow relative paths that resolve to a parent directory. + if (NS_tstrstr(path, NS_T("..")) != nullptr) { + LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); + return nullptr; + } + + return path; +} + +static NS_tchar* +get_quoted_path(const NS_tchar *path) +{ + size_t lenQuote = NS_tstrlen(kQuote); + size_t lenPath = NS_tstrlen(path); + size_t len = lenQuote + lenPath + lenQuote + 1; + + NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar)); + if (!s) + return nullptr; + + NS_tchar *c = s; + NS_tstrcpy(c, kQuote); + c += lenQuote; + NS_tstrcat(c, path); + c += lenPath; + NS_tstrcat(c, kQuote); + c += lenQuote; + *c = NS_T('\0'); + c++; + return s; +} + +static void ensure_write_permissions(const NS_tchar *path) +{ +#ifdef XP_WIN + (void) _wchmod(path, _S_IREAD | _S_IWRITE); +#else + struct stat fs; + if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) { + (void)chmod(path, fs.st_mode | S_IWUSR); + } +#endif +} + +static int ensure_remove(const NS_tchar *path) +{ + ensure_write_permissions(path); + int rv = NS_tremove(path); + if (rv) + LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return rv; +} + +// Remove the directory pointed to by path and all of its files and sub-directories. +static int ensure_remove_recursive(const NS_tchar *path, + bool continueEnumOnFailure = false) +{ + // We use lstat rather than stat here so that we can successfully remove + // symlinks. + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) { + // This error is benign + return rv; + } + if (!S_ISDIR(sInfo.st_mode)) { + return ensure_remove(path); + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) { + LOG(("ensure_remove_recursive: unable to open directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + rv = ensure_remove_recursive(childPath); + if (rv && !continueEnumOnFailure) { + break; + } + } + } + + NS_tclosedir(dir); + + if (rv == OK) { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) { + LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} + +static bool is_read_only(const NS_tchar *flags) +{ + size_t length = NS_tstrlen(flags); + if (length == 0) + return false; + + // Make sure the string begins with "r" + if (flags[0] != NS_T('r')) + return false; + + // Look for "r+" or "r+b" + if (length > 1 && flags[1] == NS_T('+')) + return false; + + // Look for "rb+" + if (NS_tstrcmp(flags, NS_T("rb+")) == 0) + return false; + + return true; +} + +static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options) +{ + ensure_write_permissions(path); + FILE* f = NS_tfopen(path, flags); + if (is_read_only(flags)) { + // Don't attempt to modify the file permissions if the file is being opened + // in read-only mode. + return f; + } + if (NS_tchmod(path, options) != 0) { + if (f != nullptr) { + fclose(f); + } + return nullptr; + } + struct NS_tstat_t ss; + if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) { + if (f != nullptr) { + fclose(f); + } + return nullptr; + } + return f; +} + +// Ensure that the directory containing this file exists. +static int ensure_parent_dir(const NS_tchar *path) +{ + int rv = OK; + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/')); + if (slash) { + *slash = NS_T('\0'); + rv = ensure_parent_dir(path); + // Only attempt to create the directory if we're not at the root + if (rv == OK && *path) { + rv = NS_tmkdir(path, 0755); + // If the directory already exists, then ignore the error. + if (rv < 0 && errno != EEXIST) { + LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \ + "err: %d", path, errno)); + rv = WRITE_ERROR; + } else { + rv = OK; + } + } + *slash = NS_T('/'); + } + return rv; +} + +#ifdef XP_UNIX +static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest) +{ + // Copy symlinks by creating a new symlink to the same target + NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')}; + int rv = readlink(path, target, MAXPATHLEN); + if (rv == -1) { + LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + rv = symlink(target, dest); + if (rv == -1) { + LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d", + dest, target, errno)); + return READ_ERROR; + } + return 0; +} +#endif + +#if MAYBE_USE_HARD_LINKS +/* + * Creates a hardlink (destFilename) which points to the existing file + * (srcFilename). + * + * @return 0 if successful, an error otherwise + */ + +static int +create_hard_link(const NS_tchar *srcFilename, const NS_tchar *destFilename) +{ + if (link(srcFilename, destFilename) < 0) { + LOG(("link(%s, %s) failed errno = %d", srcFilename, destFilename, errno)); + return WRITE_ERROR; + } + return OK; +} +#endif + +// Copy the file named path onto a new file named dest. +static int ensure_copy(const NS_tchar *path, const NS_tchar *dest) +{ +#ifdef XP_WIN + // Fast path for Windows + bool result = CopyFileW(path, dest, false); + if (!result) { + LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x", + path, dest, GetLastError())); + return WRITE_ERROR_FILE_COPY; + } + return OK; +#else + struct NS_tstat_t ss; + int rv = NS_tlstat(path, &ss); + if (rv) { + LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + +#ifdef XP_UNIX + if (S_ISLNK(ss.st_mode)) { + return ensure_copy_symlink(path, dest); + } +#endif + +#if MAYBE_USE_HARD_LINKS + if (sUseHardLinks) { + if (!create_hard_link(path, dest)) { + return OK; + } + // Since we failed to create the hard link, fall through and copy the file. + sUseHardLinks = false; + } +#endif + + AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode)); + if (!infile) { + LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode)); + if (!outfile) { + LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d", + dest, errno)); + return WRITE_ERROR; + } + + // This block size was chosen pretty arbitrarily but seems like a reasonable + // compromise. For example, the optimal block size on a modern OS X machine + // is 100k */ + const int blockSize = 32 * 1024; + void* buffer = malloc(blockSize); + if (!buffer) + return UPDATER_MEM_ERROR; + + while (!feof(infile.get())) { + size_t read = fread(buffer, 1, blockSize, infile); + if (ferror(infile.get())) { + LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", + path, errno)); + free(buffer); + return READ_ERROR; + } + + size_t written = 0; + + while (written < read) { + size_t chunkWritten = fwrite(buffer, 1, read - written, outfile); + if (chunkWritten <= 0) { + LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", + dest, errno)); + free(buffer); + return WRITE_ERROR_FILE_COPY; + } + + written += chunkWritten; + } + } + + rv = NS_tchmod(dest, ss.st_mode); + + free(buffer); + return rv; +#endif +} + +template +struct copy_recursive_skiplist { + NS_tchar paths[N][MAXPATHLEN]; + + void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) { + NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix); + } + + bool find(const NS_tchar *path) { + for (int i = 0; i < static_cast(N); ++i) { + if (!NS_tstricmp(paths[i], path)) { + return true; + } + } + return false; + } +}; + +// Copy all of the files and subdirectories under path to a new directory named dest. +// The path names in the skiplist will be skipped and will not be copied. +template +static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest, + copy_recursive_skiplist& skiplist) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) { + LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + +#ifdef XP_UNIX + if (S_ISLNK(sInfo.st_mode)) { + return ensure_copy_symlink(path, dest); + } +#endif + + if (!S_ISDIR(sInfo.st_mode)) { + return ensure_copy(path, dest); + } + + rv = NS_tmkdir(dest, sInfo.st_mode); + if (rv < 0 && errno != EEXIST) { + LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return WRITE_ERROR; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) { + LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + if (skiplist.find(childPath)) { + continue; + } + NS_tchar childPathDest[MAXPATHLEN]; + NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]), + NS_T("%s/%s"), dest, entry->d_name); + rv = ensure_copy_recursive(childPath, childPathDest, skiplist); + if (rv) { + break; + } + } + } + NS_tclosedir(dir); + return rv; +} + +// Renames the specified file to the new file specified. If the destination file +// exists it is removed. +static int rename_file(const NS_tchar *spath, const NS_tchar *dpath, + bool allowDirs = false) +{ + int rv = ensure_parent_dir(dpath); + if (rv) + return rv; + + struct NS_tstat_t spathInfo; + rv = NS_tstat(spath, &spathInfo); + if (rv) { + LOG(("rename_file: failed to read file status info: " LOG_S ", " \ + "err: %d", spath, errno)); + return READ_ERROR; + } + + if (!S_ISREG(spathInfo.st_mode)) { + if (allowDirs && !S_ISDIR(spathInfo.st_mode)) { + LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d", + spath, errno)); + return RENAME_ERROR_EXPECTED_FILE; + } else { + LOG(("rename_file: proceeding to rename the directory")); + } + } + + if (!NS_taccess(dpath, F_OK)) { + if (ensure_remove(dpath)) { + LOG(("rename_file: destination file exists and could not be " \ + "removed: " LOG_S, dpath)); + return WRITE_ERROR_DELETE_FILE; + } + } + + if (NS_trename(spath, dpath) != 0) { + LOG(("rename_file: failed to rename file - src: " LOG_S ", " \ + "dst:" LOG_S ", err: %d", spath, dpath, errno)); + return WRITE_ERROR; + } + + return OK; +} + +#ifdef XP_WIN +// Remove the directory pointed to by path and all of its files and +// sub-directories. If a file is in use move it to the tobedeleted directory +// and attempt to schedule removal of the file on reboot +static int remove_recursive_on_reboot(const NS_tchar *path, const NS_tchar *deleteDir) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) { + // This error is benign + return rv; + } + + if (!S_ISDIR(sInfo.st_mode)) { + NS_tchar tmpDeleteFile[MAXPATHLEN]; + GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile); + NS_tremove(tmpDeleteFile); + rv = rename_file(path, tmpDeleteFile, false); + if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { + LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: " + LOG_S, rv ? path : tmpDeleteFile)); + } else { + LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of " + "file: " LOG_S, rv ? path : tmpDeleteFile)); + } + return rv; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) { + LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S + ", rv: %d, err: %d", + path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + // There is no need to check the return value of this call since this + // function is only called after an update is successful and there is not + // much that can be done to recover if it isn't successful. There is also + // no need to log the value since it will have already been logged. + remove_recursive_on_reboot(childPath, deleteDir); + } + } + + NS_tclosedir(dir); + + if (rv == OK) { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) { + LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} +#endif + +//----------------------------------------------------------------------------- + +// Create a backup of the specified file by renaming it. +static int backup_create(const NS_tchar *path) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + return rename_file(path, backup); +} + +// Rename the backup of the specified file that was created by renaming it back +// to the original file. +static int backup_restore(const NS_tchar *path, const NS_tchar *relPath) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + NS_tchar relBackup[MAXPATHLEN]; + NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), + NS_T("%s") BACKUP_EXT, relPath); + + if (NS_taccess(backup, F_OK)) { + LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup)); + return OK; + } + + return rename_file(backup, path); +} + +// Discard the backup of the specified file that was created by renaming it. +static int backup_discard(const NS_tchar *path, const NS_tchar *relPath) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + NS_tchar relBackup[MAXPATHLEN]; + NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), + NS_T("%s") BACKUP_EXT, relPath); + + // Nothing to discard + if (NS_taccess(backup, F_OK)) { + return OK; + } + + int rv = ensure_remove(backup); +#if defined(XP_WIN) + if (rv && !sStagedUpdate && !sReplaceRequest) { + LOG(("backup_discard: unable to remove: " LOG_S, relBackup)); + NS_tchar path[MAXPATHLEN]; + GetTempFileNameW(gDeleteDirPath, L"moz", 0, path); + if (rename_file(backup, path)) { + LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S, + relBackup, relPath)); + return WRITE_ERROR_DELETE_BACKUP; + } + // The MoveFileEx call to remove the file on OS reboot will fail if the + // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key + // but this is ok since the installer / uninstaller will delete the + // directory containing the file along with its contents after an update is + // applied, on reinstall, and on uninstall. + if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { + LOG(("backup_discard: file renamed and will be removed on OS " \ + "reboot: " LOG_S, relPath)); + } else { + LOG(("backup_discard: failed to schedule OS reboot removal of " \ + "file: " LOG_S, relPath)); + } + } +#else + if (rv) + return WRITE_ERROR_DELETE_BACKUP; +#endif + + return OK; +} + +// Helper function for post-processing a temporary backup. +static void backup_finish(const NS_tchar *path, const NS_tchar *relPath, + int status) +{ + if (status == OK) + backup_discard(path, relPath); + else + backup_restore(path, relPath); +} + +//----------------------------------------------------------------------------- + +static int DoUpdate(); + +class Action +{ +public: + Action() : mProgressCost(1), mNext(nullptr) { } + virtual ~Action() { } + + virtual int Parse(NS_tchar *line) = 0; + + // Do any preprocessing to ensure that the action can be performed. Execute + // will be called if this Action and all others return OK from this method. + virtual int Prepare() = 0; + + // Perform the operation. Return OK to indicate success. After all actions + // have been executed, Finish will be called. A requirement of Execute is + // that its operation be reversable from Finish. + virtual int Execute() = 0; + + // Finish is called after execution of all actions. If status is OK, then + // all actions were successfully executed. Otherwise, some action failed. + virtual void Finish(int status) = 0; + + int mProgressCost; +private: + Action* mNext; + + friend class ActionList; +}; + +class RemoveFile : public Action +{ +public: + RemoveFile() : mSkip(0) { } + + int Parse(NS_tchar *line); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + mozilla::UniquePtr mFile; + mozilla::UniquePtr mRelPath; + int mSkip; +}; + +int +RemoveFile::Parse(NS_tchar *line) +{ + // format "" + + NS_tchar * validPath = get_valid_path(&line); + if (!validPath) { + return PARSE_ERROR; + } + + mRelPath = mozilla::MakeUnique(MAXPATHLEN); + NS_tstrcpy(mRelPath.get(), validPath); + + mFile.reset(get_full_path(validPath)); + if (!mFile) { + return PARSE_ERROR; + } + + return OK; +} + +int +RemoveFile::Prepare() +{ + // Skip the file if it already doesn't exist. + int rv = NS_taccess(mFile.get(), F_OK); + if (rv) { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get())); + + // Make sure that we're actually a file... + struct NS_tstat_t fileInfo; + rv = NS_tstat(mFile.get(), &fileInfo); + if (rv) { + LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(), + errno)); + return READ_ERROR; + } + + if (!S_ISREG(fileInfo.st_mode)) { + LOG(("path present, but not a file: " LOG_S, mFile.get())); + return DELETE_ERROR_EXPECTED_FILE; + } + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/')); + if (slash) { + *slash = NS_T('\0'); + rv = NS_taccess(mFile.get(), W_OK); + *slash = NS_T('/'); + } else { + rv = NS_taccess(NS_T("."), W_OK); + } + + if (rv) { + LOG(("access failed: %d", errno)); + return WRITE_ERROR_FILE_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveFile::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get())); + + // The file is checked for existence here and in Prepare since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mFile.get(), F_OK); + if (rv) { + LOG(("file cannot be removed because it does not exist; skipping")); + mSkip = 1; + return OK; + } + + if (sStagedUpdate) { + // Staged updates don't need backup files so just remove it. + rv = ensure_remove(mFile.get()); + if (rv) { + return rv; + } + } else { + // Rename the old file. It will be removed in Finish. + rv = backup_create(mFile.get()); + if (rv) { + LOG(("backup_create failed: %d", rv)); + return rv; + } + } + + return OK; +} + +void +RemoveFile::Finish(int status) +{ + if (mSkip) { + return; + } + + LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get())); + + // Staged updates don't create backup files. + if (!sStagedUpdate) { + backup_finish(mFile.get(), mRelPath.get(), status); + } +} + +class RemoveDir : public Action +{ +public: + RemoveDir() : mSkip(0) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // check that the source dir exists + virtual int Execute(); + virtual void Finish(int status); + +private: + mozilla::UniquePtr mDir; + mozilla::UniquePtr mRelPath; + int mSkip; +}; + +int +RemoveDir::Parse(NS_tchar *line) +{ + // format "/" + + NS_tchar * validPath = get_valid_path(&line, true); + if (!validPath) { + return PARSE_ERROR; + } + + mRelPath = mozilla::MakeUnique(MAXPATHLEN); + NS_tstrcpy(mRelPath.get(), validPath); + + mDir.reset(get_full_path(validPath)); + if (!mDir) { + return PARSE_ERROR; + } + + return OK; +} + +int +RemoveDir::Prepare() +{ + // We expect the directory to exist if we are to remove it. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get())); + + // Make sure that we're actually a dir. + struct NS_tstat_t dirInfo; + rv = NS_tstat(mDir.get(), &dirInfo); + if (rv) { + LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(), + errno)); + return READ_ERROR; + } + + if (!S_ISDIR(dirInfo.st_mode)) { + LOG(("path present, but not a directory: " LOG_S, mRelPath.get())); + return DELETE_ERROR_EXPECTED_DIR; + } + + rv = NS_taccess(mDir.get(), W_OK); + if (rv) { + LOG(("access failed: %d, %d", rv, errno)); + return WRITE_ERROR_DIR_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveDir::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get())); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) { + LOG(("directory no longer exists; skipping")); + mSkip = 1; + } + + return OK; +} + +void +RemoveDir::Finish(int status) +{ + if (mSkip || status != OK) + return; + + LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get())); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) { + LOG(("directory no longer exists; skipping")); + return; + } + + + if (status == OK) { + if (NS_trmdir(mDir.get())) { + LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d", + mRelPath.get(), rv, errno)); + } + } +} + +class AddFile : public Action +{ +public: + AddFile() : mAdded(false) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +private: + mozilla::UniquePtr mFile; + mozilla::UniquePtr mRelPath; + bool mAdded; +}; + +int +AddFile::Parse(NS_tchar *line) +{ + // format "" + + NS_tchar * validPath = get_valid_path(&line); + if (!validPath) { + return PARSE_ERROR; + } + + mRelPath = mozilla::MakeUnique(MAXPATHLEN); + NS_tstrcpy(mRelPath.get(), validPath); + + mFile.reset(get_full_path(validPath)); + if (!mFile) { + return PARSE_ERROR; + } + + return OK; +} + +int +AddFile::Prepare() +{ + LOG(("PREPARE ADD " LOG_S, mRelPath.get())); + + return OK; +} + +int +AddFile::Execute() +{ + LOG(("EXECUTE ADD " LOG_S, mRelPath.get())); + + int rv; + + // First make sure that we can actually get rid of any existing file. + rv = NS_taccess(mFile.get(), F_OK); + if (rv == 0) { + if (sStagedUpdate) { + // Staged updates don't need backup files so just remove it. + rv = ensure_remove(mFile.get()); + } else { + rv = backup_create(mFile.get()); + } + if (rv) { + return rv; + } + } else { + rv = ensure_parent_dir(mFile.get()); + if (rv) + return rv; + } + +#ifdef XP_WIN + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile, + MAXPATHLEN, nullptr, nullptr)) { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + rv = gArchiveReader.ExtractFile(sourcefile, mFile.get()); +#else + rv = gArchiveReader.ExtractFile(mRelPath.get(), mFile.get()); +#endif + if (!rv) { + mAdded = true; + } + return rv; +} + +void +AddFile::Finish(int status) +{ + LOG(("FINISH ADD " LOG_S, mRelPath.get())); + // Staged updates don't create backup files. + if (!sStagedUpdate) { + // When there is an update failure and a file has been added it is removed + // here since there might not be a backup to replace it. + if (status && mAdded) { + NS_tremove(mFile.get()); + } + backup_finish(mFile.get(), mRelPath.get(), status); + } +} + +class PatchFile : public Action +{ +public: + PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) { } + + virtual ~PatchFile(); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + int LoadSourceFile(FILE* ofile); + + static int sPatchIndex; + + const NS_tchar *mPatchFile; + mozilla::UniquePtr mFile; + mozilla::UniquePtr mFileRelPath; + int mPatchIndex; + MBSPatchHeader header; + unsigned char *buf; + NS_tchar spath[MAXPATHLEN]; + AutoFile mPatchStream; +}; + +int PatchFile::sPatchIndex = 0; + +PatchFile::~PatchFile() +{ + // Make sure mPatchStream gets unlocked on Windows; the system will do that, + // but not until some indeterminate future time, and we want determinism. + // Normally this happens at the end of Execute, when we close the stream; + // this call is here in case Execute errors out. +#ifdef XP_WIN + if (mPatchStream) { + UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1); + } +#endif + + // delete the temporary patch file + if (spath[0]) { + NS_tremove(spath); + } + + if (buf) { + free(buf); + } +} + +int +PatchFile::LoadSourceFile(FILE* ofile) +{ + struct stat os; + int rv = fstat(fileno((FILE *)ofile), &os); + if (rv) { + LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \ + "err: %d", mFileRelPath.get(), errno)); + return READ_ERROR; + } + + if (uint32_t(os.st_size) != header.slen) { + LOG(("LoadSourceFile: destination file size %d does not match expected size %d", + uint32_t(os.st_size), header.slen)); + return LOADSOURCE_ERROR_WRONG_SIZE; + } + + buf = (unsigned char *) malloc(header.slen); + if (!buf) { + return UPDATER_MEM_ERROR; + } + + size_t r = header.slen; + unsigned char *rb = buf; + while (r) { + const size_t count = mmin(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, ofile); + if (c != count) { + LOG(("LoadSourceFile: error reading destination file: " LOG_S, + mFileRelPath.get())); + return READ_ERROR; + } + + r -= c; + rb += c; + } + + // Verify that the contents of the source file correspond to what we expect. + + unsigned int crc = crc32(buf, header.slen); + + if (crc != header.scrc32) { + LOG(("LoadSourceFile: destination file crc %d does not match expected " \ + "crc %d", crc, header.scrc32)); + return CRC_ERROR; + } + + return OK; +} + +int +PatchFile::Parse(NS_tchar *line) +{ + // format "" "" + + // Get the path to the patch file inside of the mar + mPatchFile = mstrtok(kQuote, &line); + if (!mPatchFile) { + return PARSE_ERROR; + } + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) { + return PARSE_ERROR; + } + + NS_tchar * validPath = get_valid_path(&line); + if (!validPath) { + return PARSE_ERROR; + } + + mFileRelPath = mozilla::MakeUnique(MAXPATHLEN); + NS_tstrcpy(mFileRelPath.get(), validPath); + + mFile.reset(get_full_path(validPath)); + if (!mFile) { + return PARSE_ERROR; + } + + return OK; +} + +int +PatchFile::Prepare() +{ + LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get())); + + // extract the patch to a temporary file + mPatchIndex = sPatchIndex++; + + NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]), + NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex); + + NS_tremove(spath); + + mPatchStream = NS_tfopen(spath, NS_T("wb+")); + if (!mPatchStream) { + return WRITE_ERROR; + } + +#ifdef XP_WIN + // Lock the patch file, so it can't be messed with between + // when we're done creating it and when we go to apply it. + if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1)) { + LOG(("Couldn't lock patch file: %d", GetLastError())); + return LOCK_ERROR_PATCH_FILE; + } + + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN, + nullptr, nullptr)) { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + int rv = gArchiveReader.ExtractFileToStream(sourcefile, mPatchStream); +#else + int rv = gArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream); +#endif + + return rv; +} + +int +PatchFile::Execute() +{ + LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get())); + + fseek(mPatchStream, 0, SEEK_SET); + + int rv = MBS_ReadHeader(mPatchStream, &header); + if (rv) { + return rv; + } + + FILE *origfile = nullptr; +#ifdef XP_WIN + if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) { + // Read from the copy of the callback when patching since the callback can't + // be opened for reading to prevent the application from being launched. + origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb")); + } else { + origfile = NS_tfopen(mFile.get(), NS_T("rb")); + } +#else + origfile = NS_tfopen(mFile.get(), NS_T("rb")); +#endif + + if (!origfile) { + LOG(("unable to open destination file: " LOG_S ", err: %d", + mFileRelPath.get(), errno)); + return READ_ERROR; + } + + rv = LoadSourceFile(origfile); + fclose(origfile); + if (rv) { + LOG(("LoadSourceFile failed")); + return rv; + } + + // Rename the destination file if it exists before proceeding so it can be + // used to restore the file to its original state if there is an error. + struct NS_tstat_t ss; + rv = NS_tstat(mFile.get(), &ss); + if (rv) { + LOG(("failed to read file status info: " LOG_S ", err: %d", + mFileRelPath.get(), errno)); + return READ_ERROR; + } + + // Staged updates don't need backup files. + if (!sStagedUpdate) { + rv = backup_create(mFile.get()); + if (rv) { + return rv; + } + } + +#if defined(HAVE_POSIX_FALLOCATE) + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); + posix_fallocate(fileno((FILE *)ofile), 0, header.dlen); +#elif defined(XP_WIN) + bool shouldTruncate = true; + // Creating the file, setting the size, and then closing the file handle + // lessens fragmentation more than any other method tested. Other methods that + // have been tested are: + // 1. _chsize / _chsize_s reduced fragmentation though not completely. + // 2. _get_osfhandle and then setting the size reduced fragmentation though + // not completely. There are also reports of _get_osfhandle failing on + // mingw. + HANDLE hfile = CreateFileW(mFile.get(), + GENERIC_WRITE, + 0, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (hfile != INVALID_HANDLE_VALUE) { + if (SetFilePointer(hfile, header.dlen, + nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER && + SetEndOfFile(hfile) != 0) { + shouldTruncate = false; + } + CloseHandle(hfile); + } + + AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"), + ss.st_mode)); +#elif defined(XP_MACOSX) + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); + // Modified code from FileUtils.cpp + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen}; + // Try to get a continous chunk of disk space + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + if (rv == -1) { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + } + + if (rv != -1) { + ftruncate(fileno((FILE *)ofile), header.dlen); + } +#else + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); +#endif + + if (ofile == nullptr) { + LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(), + errno)); + return WRITE_ERROR_OPEN_PATCH_FILE; + } + +#ifdef XP_WIN + if (!shouldTruncate) { + fseek(ofile, 0, SEEK_SET); + } +#endif + + rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile); + + // Go ahead and do a bit of cleanup now to minimize runtime overhead. + // Make sure mPatchStream gets unlocked on Windows; the system will do that, + // but not until some indeterminate future time, and we want determinism. +#ifdef XP_WIN + UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1); +#endif + // Set mPatchStream to nullptr to make AutoFile close the file, + // so it can be deleted on Windows. + mPatchStream = nullptr; + NS_tremove(spath); + spath[0] = NS_T('\0'); + free(buf); + buf = nullptr; + + return rv; +} + +void +PatchFile::Finish(int status) +{ + LOG(("FINISH PATCH " LOG_S, mFileRelPath.get())); + + // Staged updates don't create backup files. + if (!sStagedUpdate) { + backup_finish(mFile.get(), mFileRelPath.get(), status); + } +} + +class AddIfFile : public AddFile +{ +public: + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + mozilla::UniquePtr mTestFile; +}; + +int +AddIfFile::Parse(NS_tchar *line) +{ + // format "" "" + + mTestFile.reset(get_full_path(get_valid_path(&line))); + if (!mTestFile) { + return PARSE_ERROR; + } + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) { + return PARSE_ERROR; + } + + return AddFile::Parse(line); +} + +int +AddIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile.get(), F_OK)) { + mTestFile = nullptr; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class AddIfNotFile : public AddFile +{ +public: + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + mozilla::UniquePtr mTestFile; +}; + +int +AddIfNotFile::Parse(NS_tchar *line) +{ + // format "" "" + + mTestFile.reset(get_full_path(get_valid_path(&line))); + if (!mTestFile) { + return PARSE_ERROR; + } + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) { + return PARSE_ERROR; + } + + return AddFile::Parse(line); +} + +int +AddIfNotFile::Prepare() +{ + // If the test file exists, then skip this action. + if (!NS_taccess(mTestFile.get(), F_OK)) { + mTestFile = NULL; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfNotFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfNotFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class PatchIfFile : public PatchFile +{ +public: + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + mozilla::UniquePtr mTestFile; +}; + +int +PatchIfFile::Parse(NS_tchar *line) +{ + // format "" "" "" + + mTestFile.reset(get_full_path(get_valid_path(&line))); + if (!mTestFile) { + return PARSE_ERROR; + } + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) { + return PARSE_ERROR; + } + + return PatchFile::Parse(line); +} + +int +PatchIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile.get(), F_OK)) { + mTestFile = nullptr; + return OK; + } + + return PatchFile::Prepare(); +} + +int +PatchIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return PatchFile::Execute(); +} + +void +PatchIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + PatchFile::Finish(status); +} + +//----------------------------------------------------------------------------- + +#ifdef XP_WIN +#include "nsWindowsRestart.cpp" +#include "nsWindowsHelpers.h" +#include "uachelper.h" +#include "pathhash.h" + +/** + * Launch the post update application (helper.exe). It takes in the path of the + * callback application to calculate the path of helper.exe. For service updates + * this is called from both the system account and the current user account. + * + * @param installationDir The path to the callback application binary. + * @param updateInfoDir The directory where update info is stored. + * @return true if there was no error starting the process. + */ +bool +LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir) +{ + WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; + wcsncpy(workingDirectory, installationDir, MAX_PATH); + + // Launch helper.exe to perform post processing (e.g. registry and log file + // modifications) for the update. + WCHAR inifile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(inifile, installationDir, MAX_PATH); + if (!PathAppendSafe(inifile, L"updater.ini")) { + return false; + } + + WCHAR exefile[MAX_PATH + 1]; + WCHAR exearg[MAX_PATH + 1]; + WCHAR exeasync[10]; + bool async = true; + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, + exefile, MAX_PATH + 1, inifile)) { + return false; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, + MAX_PATH + 1, inifile)) { + return false; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", + exeasync, + sizeof(exeasync)/sizeof(exeasync[0]), + inifile)) { + return false; + } + + // The relative path must not contain directory traversals, current directory, + // or colons. + if (wcsstr(exefile, L"..") != nullptr || + wcsstr(exefile, L"./") != nullptr || + wcsstr(exefile, L".\\") != nullptr || + wcsstr(exefile, L":") != nullptr) { + return false; + } + + // The relative path must not start with a decimal point, backslash, or + // forward slash. + if (exefile[0] == L'.' || + exefile[0] == L'\\' || + exefile[0] == L'/') { + return false; + } + + WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(exefullpath, installationDir, MAX_PATH); + if (!PathAppendSafe(exefullpath, exefile)) { + return false; + } + + if (!IsValidFullPath(exefullpath)) { + return false; + } + +#if !defined(TEST_UPDATER) && defined(MOZ_MAINTENANCE_SERVICE) + if (sUsingService && + !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) { + return false; + } +#endif + + WCHAR dlogFile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) { + return false; + } + + WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(slogFile, updateInfoDir, MAX_PATH); + if (!PathAppendSafe(slogFile, L"update.log")) { + return false; + } + + WCHAR dummyArg[14] = { L'\0' }; + wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1); + + size_t len = wcslen(exearg) + wcslen(dummyArg); + WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); + if (!cmdline) { + return false; + } + + wcsncpy(cmdline, dummyArg, len); + wcscat(cmdline, exearg); + + if (sUsingService || + !_wcsnicmp(exeasync, L"false", 6) || + !_wcsnicmp(exeasync, L"0", 2)) { + async = false; + } + + // We want to launch the post update helper app to update the Windows + // registry even if there is a failure with removing the uninstall.update + // file or copying the update.log file. + CopyFileW(slogFile, dlogFile, false); + + STARTUPINFOW si = {sizeof(si), 0}; + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + + bool ok = CreateProcessW(exefullpath, + cmdline, + nullptr, // no special security attributes + nullptr, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + nullptr, // inherit my environment + workingDirectory, + &si, + &pi); + free(cmdline); + if (ok) { + if (!async) { + WaitForSingleObject(pi.hProcess, INFINITE); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return ok; +} + +#endif + +static void +LaunchCallbackApp(const NS_tchar *workingDir, + int argc, + NS_tchar **argv, + bool usingService) +{ + putenv(const_cast("NO_EM_RESTART=")); + putenv(const_cast("MOZ_LAUNCHED_CHILD=1")); + + // Run from the specified working directory (see bug 312360). This is not + // necessary on Windows CE since the application that launches the updater + // passes the working directory as an --environ: command line argument. + if (NS_tchdir(workingDir) != 0) { + LOG(("Warning: chdir failed")); + } + +#if defined(USE_EXECV) + execv(argv[0], argv); +#elif defined(XP_MACOSX) + LaunchChild(argc, (const char**)argv); +#elif defined(XP_WIN) + // Do not allow the callback to run when running an update through the + // service as session 0. The unelevated updater.exe will do the launching. + if (!usingService) { + WinLaunchChild(argv[0], argc, argv, nullptr); + } +#else +# warning "Need implementaton of LaunchCallbackApp" +#endif +} + +static bool +WriteStatusFile(const char* aStatus) +{ + NS_tchar filename[MAXPATHLEN] = {NS_T('\0')}; +#if defined(XP_WIN) + // The temp file is not removed on failure since there is client code that + // will remove it. + if (GetTempFileNameW(gPatchDirPath, L"sta", 0, filename) == 0) { + return false; + } +#else + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); +#endif + + // Make sure that the directory for the update status file exists + if (ensure_parent_dir(filename)) { + return false; + } + + // This is scoped to make the AutoFile close the file so it is possible to + // move the temp file to the update.status file on Windows. + { + AutoFile file(NS_tfopen(filename, NS_T("wb+"))); + if (file == nullptr) { + return false; + } + + if (fwrite(aStatus, strlen(aStatus), 1, file) != 1) { + return false; + } + } + +#if defined(XP_WIN) + NS_tchar dstfilename[MAXPATHLEN] = {NS_T('\0')}; + NS_tsnprintf(dstfilename, sizeof(dstfilename)/sizeof(dstfilename[0]), + NS_T("%s\\update.status"), gPatchDirPath); + if (MoveFileExW(filename, dstfilename, MOVEFILE_REPLACE_EXISTING) == 0) { + return false; + } +#endif + + return true; +} + +static void +WriteStatusFile(int status) +{ + const char *text; + + char buf[32]; + if (status == OK) { + if (sStagedUpdate) { + text = "applied\n"; + } else { + text = "succeeded\n"; + } + } else { + snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status); + text = buf; + } + + WriteStatusFile(text); +} + +#ifdef MOZ_MAINTENANCE_SERVICE +/* + * Read the update.status file and sets isPendingService to true if + * the status is set to pending-service. + * + * @param isPendingService Out parameter for specifying if the status + * is set to pending-service or not. + * @return true if the information was retrieved and it is pending + * or pending-service. +*/ +static bool +IsUpdateStatusPendingService() +{ + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kPendingService[] = "pending-service"; + const char kAppliedService[] = "applied-service"; + + return (strncmp(buf, kPendingService, + sizeof(kPendingService) - 1) == 0) || + (strncmp(buf, kAppliedService, + sizeof(kAppliedService) - 1) == 0); +} +#endif + +#ifdef XP_WIN +/* + * Read the update.status file and sets isSuccess to true if + * the status is set to succeeded. + * + * @param isSucceeded Out parameter for specifying if the status + * is set to succeeded or not. + * @return true if the information was retrieved and it is succeeded. +*/ +static bool +IsUpdateStatusSucceeded(bool &isSucceeded) +{ + isSucceeded = false; + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kSucceeded[] = "succeeded"; + isSucceeded = strncmp(buf, kSucceeded, + sizeof(kSucceeded) - 1) == 0; + return true; +} +#endif + +/* + * Copy the entire contents of the application installation directory to the + * destination directory for the update process. + * + * @return 0 if successful, an error code otherwise. + */ +static int +CopyInstallDirToDestDir() +{ + // These files should not be copied over to the updated app +#ifdef XP_WIN +#define SKIPLIST_COUNT 3 +#elif XP_MACOSX +#define SKIPLIST_COUNT 0 +#else +#define SKIPLIST_COUNT 2 +#endif + copy_recursive_skiplist skiplist; +#ifndef XP_MACOSX + skiplist.append(0, gInstallDirPath, NS_T("updated")); + skiplist.append(1, gInstallDirPath, NS_T("updates/0")); +#ifdef XP_WIN + skiplist.append(2, gInstallDirPath, NS_T("updated.update_in_progress.lock")); +#endif +#endif + + return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist); +} + +/* + * Replace the application installation directory with the destination + * directory in order to finish a staged update task + * + * @return 0 if successful, an error code otherwise. + */ +static int +ProcessReplaceRequest() +{ + // The replacement algorithm is like this: + // 1. Move destDir to tmpDir. In case of failure, abort. + // 2. Move newDir to destDir. In case of failure, revert step 1 and abort. + // 3. Delete tmpDir (or defer it to the next reboot). + +#ifdef XP_MACOSX + NS_tchar destDir[MAXPATHLEN]; + NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]), + NS_T("%s/Contents"), gInstallDirPath); +#elif XP_WIN + // Windows preserves the case of the file/directory names. We use the + // GetLongPathName API in order to get the correct case for the directory + // name, so that if the user has used a different case when launching the + // application, the installation directory's name does not change. + NS_tchar destDir[MAXPATHLEN]; + if (!GetLongPathNameW(gInstallDirPath, destDir, + sizeof(destDir)/sizeof(destDir[0]))) { + return NO_INSTALLDIR_ERROR; + } +#else + NS_tchar* destDir = gInstallDirPath; +#endif + + NS_tchar tmpDir[MAXPATHLEN]; + NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]), + NS_T("%s.bak"), destDir); + + NS_tchar newDir[MAXPATHLEN]; + NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]), +#ifdef XP_MACOSX + NS_T("%s/Contents"), + gWorkingDirPath); +#else + NS_T("%s.bak/updated"), + gInstallDirPath); +#endif + + // First try to remove the possibly existing temp directory, because if this + // directory exists, we will fail to rename destDir. + // No need to error check here because if this fails, we will fail in the + // next step anyways. + ensure_remove_recursive(tmpDir); + + LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")", + destDir, tmpDir)); + int rv = rename_file(destDir, tmpDir, true); +#ifdef XP_WIN + // On Windows, if Firefox is launched using the shortcut, it will hold a handle + // to its installation directory open, which might not get released in time. + // Therefore we wait a little bit here to see if the handle is released. + // If it's not released, we just fail to perform the replace request. + const int max_retries = 10; + int retries = 0; + while (rv == WRITE_ERROR && (retries++ < max_retries)) { + LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \ + "File: " LOG_S ". Last error: %d, err: %d", retries, + destDir, GetLastError(), rv)); + + Sleep(100); + + rv = rename_file(destDir, tmpDir, true); + } +#endif + if (rv) { + // The status file will have 'pending' written to it so there is no value in + // returning an error specific for this failure. + LOG(("Moving destDir to tmpDir failed, err: %d", rv)); + return rv; + } + + LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + rv = rename_file(newDir, destDir, true); +#ifdef XP_MACOSX + if (rv) { + LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + copy_recursive_skiplist<0> skiplist; + rv = ensure_copy_recursive(newDir, destDir, skiplist); + } +#endif + if (rv) { + LOG(("Moving newDir to destDir failed, err: %d", rv)); + LOG(("Now, try to move tmpDir back to destDir")); + ensure_remove_recursive(destDir); + int rv2 = rename_file(tmpDir, destDir, true); + if (rv2) { + LOG(("Moving tmpDir back to destDir failed, err: %d", rv2)); + } + // The status file will be have 'pending' written to it so there is no value + // in returning an error specific for this failure. + return rv; + } + +#if !defined(XP_WIN) && !defined(XP_MACOSX) + // Platforms that have their updates directory in the installation directory + // need to have the last-update.log and backup-update.log files moved from the + // old installation directory to the new installation directory. + NS_tchar tmpLog[MAXPATHLEN]; + NS_tsnprintf(tmpLog, sizeof(tmpLog)/sizeof(tmpLog[0]), + NS_T("%s/updates/last-update.log"), tmpDir); + if (!NS_taccess(tmpLog, F_OK)) { + NS_tchar destLog[MAXPATHLEN]; + NS_tsnprintf(destLog, sizeof(destLog)/sizeof(destLog[0]), + NS_T("%s/updates/last-update.log"), destDir); + NS_tremove(destLog); + NS_trename(tmpLog, destLog); + } +#endif + + LOG(("Now, remove the tmpDir")); + rv = ensure_remove_recursive(tmpDir, true); + if (rv) { + LOG(("Removing tmpDir failed, err: %d", rv)); +#ifdef XP_WIN + NS_tchar deleteDir[MAXPATHLEN]; + NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]), + NS_T("%s\\%s"), destDir, DELETE_DIR); + // Attempt to remove the tobedeleted directory and then recreate it if it + // was successfully removed. + _wrmdir(deleteDir); + if (NS_taccess(deleteDir, F_OK)) { + NS_tmkdir(deleteDir, 0755); + } + remove_recursive_on_reboot(tmpDir, deleteDir); +#endif + } + +#ifdef XP_MACOSX + // On OS X, we we need to remove the staging directory after its Contents + // directory has been moved. + NS_tchar updatedAppDir[MAXPATHLEN]; + NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]), + NS_T("%s/Updated.app"), gPatchDirPath); + ensure_remove_recursive(updatedAppDir); +#endif + + gSucceeded = true; + + return 0; +} + +#ifdef XP_WIN +static void +WaitForServiceFinishThread(void *param) +{ + // We wait at most 10 minutes, we already waited 5 seconds previously + // before deciding to show this UI. + WaitForServiceStop(SVC_NAME, 595); + QuitProgressUI(); +} +#endif + +#ifdef MOZ_VERIFY_MAR_SIGNATURE +/** + * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini + * + * @param path The path to the ini file that is to be read + * @param results A pointer to the location to store the read strings + * @return OK on success + */ +static int +ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results) +{ + const unsigned int kNumStrings = 1; + const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0"; + char updater_strings[kNumStrings][MAX_TEXT_LEN]; + + int result = ReadStrings(path, kUpdaterKeys, kNumStrings, + updater_strings, "Settings"); + + strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1); + results->MARChannelID[MAX_TEXT_LEN - 1] = 0; + + return result; +} +#endif + +static int +GetUpdateFileName(NS_tchar *fileName, int maxChars) +{ +#if defined(MOZ_WIDGET_GONK) + // If an update.link file exists, then it will contain the name + // of the update file (terminated by a newline). + + NS_tchar linkFileName[MAXPATHLEN]; + NS_tsnprintf(linkFileName, sizeof(linkFileName)/sizeof(linkFileName[0]), + NS_T("%s/update.link"), gPatchDirPath); + AutoFile linkFile(NS_tfopen(linkFileName, NS_T("rb"))); + if (linkFile == nullptr) { + NS_tsnprintf(fileName, maxChars, + NS_T("%s/update.mar"), gPatchDirPath); + return OK; + } + + char dataFileName[MAXPATHLEN]; + size_t bytesRead; + + if ((bytesRead = fread(dataFileName, 1, sizeof(dataFileName)-1, linkFile)) <= 0) { + *fileName = NS_T('\0'); + return READ_ERROR; + } + if (dataFileName[bytesRead-1] == '\n') { + // Strip trailing newline (for \n and \r\n) + bytesRead--; + } + if (dataFileName[bytesRead-1] == '\r') { + // Strip trailing CR (for \r, \r\n) + bytesRead--; + } + dataFileName[bytesRead] = '\0'; + + strncpy(fileName, dataFileName, maxChars-1); + fileName[maxChars-1] = '\0'; +#else + // We currently only support update.link files under GONK + NS_tsnprintf(fileName, maxChars, + NS_T("%s/update.mar"), gPatchDirPath); +#endif + return OK; +} + +static void +UpdateThreadFunc(void *param) +{ + // open ZIP archive and process... + int rv; + if (sReplaceRequest) { + rv = ProcessReplaceRequest(); + } else { + NS_tchar dataFile[MAXPATHLEN]; + rv = GetUpdateFileName(dataFile, sizeof(dataFile)/sizeof(dataFile[0])); + if (rv == OK) { + rv = gArchiveReader.Open(dataFile); + } + +#ifdef MOZ_VERIFY_MAR_SIGNATURE + if (rv == OK) { +#ifdef XP_WIN + HKEY baseKey = nullptr; + wchar_t valueName[] = L"Image Path"; + wchar_t rasenh[] = L"rsaenh.dll"; + bool reset = false; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0", + 0, KEY_READ | KEY_WRITE, + &baseKey) == ERROR_SUCCESS) { + wchar_t path[MAX_PATH + 1]; + DWORD size = sizeof(path); + DWORD type; + if (RegQueryValueExW(baseKey, valueName, 0, &type, + (LPBYTE)path, &size) == ERROR_SUCCESS) { + if (type == REG_SZ && wcscmp(path, rasenh) == 0) { + wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll"; + if (RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenhFullPath, + sizeof(rasenhFullPath)) == ERROR_SUCCESS) { + reset = true; + } + } + } + } +#endif + rv = gArchiveReader.VerifySignature(); +#ifdef XP_WIN + if (baseKey) { + if (reset) { + RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenh, + sizeof(rasenh)); + } + RegCloseKey(baseKey); + } +#endif + } + + if (rv == OK) { + if (rv == OK) { + NS_tchar updateSettingsPath[MAX_TEXT_LEN]; + NS_tsnprintf(updateSettingsPath, + sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]), +#ifdef XP_MACOSX + NS_T("%s/Contents/Resources/update-settings.ini"), +#else + NS_T("%s/update-settings.ini"), +#endif + gWorkingDirPath); + MARChannelStringTable MARStrings; + if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) { + // If we can't read from update-settings.ini then we shouldn't impose + // a MAR restriction. Some installations won't even include this file. + MARStrings.MARChannelID[0] = '\0'; + } + + rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID, + MOZ_APP_VERSION); + } + } +#endif + + if (rv == OK && sStagedUpdate && !sIsOSUpdate) { +#ifdef TEST_UPDATER + // The MOZ_TEST_SKIP_UPDATE_STAGE environment variable prevents copying + // the files in dist/bin in the test updater when staging an update since + // this can cause tests to timeout. + if (EnvHasValue("MOZ_TEST_SKIP_UPDATE_STAGE")) { + rv = OK; + } else { + rv = CopyInstallDirToDestDir(); + } +#else + rv = CopyInstallDirToDestDir(); +#endif + } + + if (rv == OK) { + rv = DoUpdate(); + gArchiveReader.Close(); + NS_tchar updatingDir[MAXPATHLEN]; + NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]), + NS_T("%s/updating"), gWorkingDirPath); + ensure_remove_recursive(updatingDir); + } + } + + if (rv && (sReplaceRequest || sStagedUpdate)) { +#ifdef XP_WIN + // On Windows, the current working directory of the process should be changed + // so that it's not locked. + if (sStagedUpdate) { + NS_tchar sysDir[MAX_PATH + 1] = { L'\0' }; + if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) { + NS_tchdir(sysDir); + } + } +#endif + ensure_remove_recursive(gWorkingDirPath); + // When attempting to replace the application, we should fall back + // to non-staged updates in case of a failure. We do this by + // setting the status to pending, exiting the updater, and + // launching the callback application. The callback application's + // startup path will see the pending status, and will start the + // updater application again in order to apply the update without + // staging. + if (sReplaceRequest) { + WriteStatusFile(sUsingService ? "pending-service" : "pending"); + } else { + WriteStatusFile(rv); + } +#ifdef TEST_UPDATER + // Some tests need to use --test-process-updates again. + putenv(const_cast("MOZ_TEST_PROCESS_UPDATES=")); +#endif + } else { + if (rv) { + LOG(("failed: %d", rv)); + } else { +#ifdef XP_MACOSX + // If the update was successful we need to update the timestamp on the + // top-level Mac OS X bundle directory so that Mac OS X's Launch Services + // picks up any major changes when the bundle is updated. + if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) { + LOG(("Couldn't set access/modification time on application bundle.")); + } +#endif + + LOG(("succeeded")); + } + WriteStatusFile(rv); + } + + LOG(("calling QuitProgressUI")); + QuitProgressUI(); +} + +#ifdef XP_MACOSX +static void +ServeElevatedUpdateThreadFunc(void* param) +{ + UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param; + gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv); + if (!gSucceeded) { + WriteStatusFile(ELEVATION_CANCELED); + } + QuitProgressUI(); +} + +void freeArguments(int argc, char** argv) +{ + for (int i = 0; i < argc; i++) { + free(argv[i]); + } + free(argv); +} +#endif + +int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv, + int callbackIndex +#ifdef XP_WIN + , const WCHAR* elevatedLockFilePath + , HANDLE updateLockFileHandle +#elif XP_MACOSX + , bool isElevated +#endif + ) +{ + if (argc > callbackIndex) { +#if defined(XP_WIN) + if (gSucceeded) { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) { + fprintf(stderr, "The post update process was not launched"); + } + + // The service update will only be executed if it is already installed. + // For first time installs of the service, the install will happen from + // the PostUpdate process. We do the service update process here + // because it's possible we are updating with updater.exe without the + // service if the service failed to apply the update. We want to update + // the service to a newer version in that case. If we are not running + // through the service, then MOZ_USING_SERVICE will not exist. + if (!sUsingService) { + StartServiceUpdate(gInstallDirPath); + } + } + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0); +#elif XP_MACOSX + if (!isElevated) { + if (gSucceeded) { + LaunchMacPostProcess(gInstallDirPath); + } +#endif + + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); +#ifdef XP_MACOSX + } // if (!isElevated) +#endif /* XP_MACOSX */ + } + return 0; +} + +int NS_main(int argc, NS_tchar **argv) +{ + // The callback is the remaining arguments starting at callbackIndex. + // The argument specified by callbackIndex is the callback executable and the + // argument prior to callbackIndex is the working directory. + const int callbackIndex = 6; + +#ifdef XP_MACOSX + bool isElevated = + strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0; + if (isElevated) { + if (!ObtainUpdaterArguments(&argc, &argv)) { + // Won't actually get here because ObtainUpdaterArguments will terminate + // the current process on failure. + return 1; + } + } +#endif + +#if defined(MOZ_WIDGET_GONK) + if (EnvHasValue("LD_PRELOAD")) { + // If the updater is launched with LD_PRELOAD set, then we wind up + // preloading libmozglue.so. Under some circumstances, this can cause + // the remount of /system to fail when going from rw to ro, so if we + // detect LD_PRELOAD we unsetenv it and relaunch ourselves without it. + // This will cause the offending preloaded library to be closed. + // + // For a variety of reasons, this is really hard to do in a safe manner + // in the parent process, so we do it here. + unsetenv("LD_PRELOAD"); + execv(argv[0], argv); + __android_log_print(ANDROID_LOG_INFO, "updater", + "execve failed: errno: %d. Exiting...", errno); + _exit(1); + } +#endif + +#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \ + !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + // On Windows and Mac we rely on native APIs to do verifications so we don't + // need to initialize NSS at all there. + // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS + // databases. + if (NSS_NoDB_Init(NULL) != SECSuccess) { + PRErrorCode error = PR_GetError(); + fprintf(stderr, "Could not initialize NSS: %s (%d)", + PR_ErrorToName(error), (int) error); + _exit(1); + } +#endif + +#ifdef XP_MACOSX + if (!isElevated) { +#endif + InitProgressUI(&argc, &argv); +#ifdef XP_MACOSX + } +#endif + + // To process an update the updater command line must at a minimum have the + // directory path containing the updater.mar file to process as the first + // argument, the install directory as the second argument, and the directory + // to apply the update to as the third argument. When the updater is launched + // by another process the PID of the parent process should be provided in the + // optional fourth argument and the updater will wait on the parent process to + // exit if the value is non-zero and the process is present. This is necessary + // due to not being able to update files that are in use on Windows. The + // optional fifth argument is the callback's working directory and the + // optional sixth argument is the callback path. The callback is the + // application to launch after updating and it will be launched when these + // arguments are provided whether the update was successful or not. All + // remaining arguments are optional and are passed to the callback when it is + // launched. + if (argc < 4) { + fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n"); +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + + // This check is also performed in workmonitor.cpp since the maintenance + // service can be called directly. + if (!IsValidFullPath(argv[1])) { + // Since the status file is written to the patch directory and the patch + // directory is invalid don't write the status file. + fprintf(stderr, "The patch directory path is not valid for this " \ + "application (" LOG_S ")\n", argv[1]); +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + // The directory containing the update information. + NS_tstrncpy(gPatchDirPath, argv[1], MAXPATHLEN); + + // This check is also performed in workmonitor.cpp since the maintenance + // service can be called directly. + if (!IsValidFullPath(argv[2])) { + WriteStatusFile(INVALID_INSTALL_DIR_PATH_ERROR); + fprintf(stderr, "The install directory path is not valid for this " \ + "application (" LOG_S ")\n", argv[2]); +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN); + gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0'); + NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH); + if (slash && !slash[1]) { + *slash = NS_T('\0'); + } + +#ifdef XP_WIN + bool useService = false; + bool testOnlyFallbackKeyExists = false; + bool noServiceFallback = false; + + // We never want the service to be used unless we build with + // the maintenance service. +#ifdef MOZ_MAINTENANCE_SERVICE + useService = IsUpdateStatusPendingService(); +#ifdef TEST_UPDATER + noServiceFallback = EnvHasValue("MOZ_NO_SERVICE_FALLBACK"); + putenv(const_cast("MOZ_NO_SERVICE_FALLBACK=")); + // Our tests run with a different apply directory for each test. + // We use this registry key on our test slaves to store the + // allowed name/issuers. + testOnlyFallbackKeyExists = DoesFallbackKeyExist(); +#endif +#endif + + // Remove everything except close window from the context menu + { + HKEY hkApp = nullptr; + RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", + 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr); + RegCloseKey(hkApp); + if (RegCreateKeyExW(HKEY_CURRENT_USER, + L"Software\\Classes\\Applications\\updater.exe", + 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr) == ERROR_SUCCESS) { + RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); + RegCloseKey(hkApp); + } + } +#endif + + // If there is a PID specified and it is not '0' then wait for the process to exit. +#ifdef XP_WIN + __int64 pid = 0; +#else + int pid = 0; +#endif + if (argc > 4) { +#ifdef XP_WIN + pid = _wtoi64(argv[4]); +#else + pid = atoi(argv[4]); +#endif + if (pid == -1) { + // This is a signal from the parent process that the updater should stage + // the update. + sStagedUpdate = true; + } else if (NS_tstrstr(argv[4], NS_T("/replace"))) { + // We're processing a request to replace the application with a staged + // update. + sReplaceRequest = true; + } + } + + // This check is also performed in workmonitor.cpp since the maintenance + // service can be called directly. + if (!IsValidFullPath(argv[3])) { + WriteStatusFile(INVALID_WORKING_DIR_PATH_ERROR); + fprintf(stderr, "The working directory path is not valid for this " \ + "application (" LOG_S ")\n", argv[3]); +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN); + gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0'); + slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH); + if (slash && !slash[1]) { + *slash = NS_T('\0'); + } + + // These checks are also performed in workmonitor.cpp since the maintenance + // service can be called directly. + if (argc > callbackIndex) { + if (!IsValidFullPath(argv[callbackIndex])) { + WriteStatusFile(INVALID_CALLBACK_PATH_ERROR); + fprintf(stderr, "The callback file path is not valid for this " \ + "application (" LOG_S ")\n", argv[callbackIndex]); +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + + size_t len = NS_tstrlen(gInstallDirPath); + NS_tchar callbackInstallDir[MAXPATHLEN] = { NS_T('\0') }; + NS_tstrncpy(callbackInstallDir, argv[callbackIndex], len); + if (NS_tstrcmp(gInstallDirPath, callbackInstallDir) != 0) { + WriteStatusFile(INVALID_CALLBACK_DIR_ERROR); + fprintf(stderr, "The callback file must be located in the " \ + "installation directory (" LOG_S ")\n", argv[callbackIndex]); +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + } + +#ifdef XP_MACOSX + if (!isElevated && !IsRecursivelyWritable(argv[2])) { + // If the app directory isn't recursively writeable, an elevated update is + // required. + UpdateServerThreadArgs threadArgs; + threadArgs.argc = argc; + threadArgs.argv = const_cast(argv); + + Thread t1; + if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) { + // Show an indeterminate progress bar while an elevated update is in + // progress. + ShowProgressUI(true); + } + t1.Join(); + + LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false); + return gSucceeded ? 0 : 1; + } +#endif + + if (EnvHasValue("MOZ_OS_UPDATE")) { + sIsOSUpdate = true; + putenv(const_cast("MOZ_OS_UPDATE=")); + } + + LogInit(gPatchDirPath, NS_T("update.log")); + + if (!WriteStatusFile("applying")) { + LOG(("failed setting status to 'applying'")); +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + + if (sStagedUpdate) { + LOG(("Performing a staged update")); + } else if (sReplaceRequest) { + LOG(("Performing a replace request")); + } + + LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath)); + LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath)); + LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath)); + +#if defined(XP_WIN) + // These checks are also performed in workmonitor.cpp since the maintenance + // service can be called directly. + if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) { + if (!sStagedUpdate && !sReplaceRequest) { + WriteStatusFile(INVALID_APPLYTO_DIR_ERROR); + LOG(("Installation directory and working directory must be the same " + "for non-staged updates. Exiting.")); + LogFinish(); + return 1; + } + + NS_tchar workingDirParent[MAX_PATH]; + NS_tsnprintf(workingDirParent, + sizeof(workingDirParent) / sizeof(workingDirParent[0]), + NS_T("%s"), gWorkingDirPath); + if (!PathRemoveFileSpecW(workingDirParent)) { + WriteStatusFile(REMOVE_FILE_SPEC_ERROR); + LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError())); + LogFinish(); + return 1; + } + + if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) { + WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR); + LOG(("The apply-to directory must be the same as or " + "a child of the installation directory! Exiting.")); + LogFinish(); + return 1; + } + } +#endif + +#ifdef MOZ_WIDGET_GONK + const char *prioEnv = getenv("MOZ_UPDATER_PRIO"); + if (prioEnv) { + int32_t prioVal; + int32_t oomScoreAdj; + int32_t ioprioClass; + int32_t ioprioLevel; + if (sscanf(prioEnv, "%d/%d/%d/%d", + &prioVal, &oomScoreAdj, &ioprioClass, &ioprioLevel) == 4) { + LOG(("MOZ_UPDATER_PRIO=%s", prioEnv)); + if (setpriority(PRIO_PROCESS, 0, prioVal)) { + LOG(("setpriority(%d) failed, errno = %d", prioVal, errno)); + } + if (ioprio_set(IOPRIO_WHO_PROCESS, 0, + IOPRIO_PRIO_VALUE(ioprioClass, ioprioLevel))) { + LOG(("ioprio_set(%d,%d) failed: errno = %d", + ioprioClass, ioprioLevel, errno)); + } + FILE *fs = fopen("/proc/self/oom_score_adj", "w"); + if (fs) { + fprintf(fs, "%d", oomScoreAdj); + fclose(fs); + } else { + LOG(("Unable to open /proc/self/oom_score_adj for writing, errno = %d", + errno)); + } + } + } +#endif + +#ifdef XP_WIN + if (pid > 0) { + HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid); + // May return nullptr if the parent process has already gone away. + // Otherwise, wait for the parent process to exit before starting the + // update. + if (parent) { + DWORD waitTime = PARENT_WAIT; + DWORD result = WaitForSingleObject(parent, waitTime); + CloseHandle(parent); + if (result != WAIT_OBJECT_0) { + return 1; + } + } + } +#else + if (pid > 0) + waitpid(pid, nullptr, 0); +#endif + +#ifdef XP_WIN +#ifdef MOZ_MAINTENANCE_SERVICE + sUsingService = EnvHasValue("MOZ_USING_SERVICE"); + putenv(const_cast("MOZ_USING_SERVICE=")); +#endif + // lastFallbackError keeps track of the last error for the service not being + // used, in case of an error when fallback is not enabled we write the + // error to the update.status file. + // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then + // we will instead fallback to not using the service and display a UAC prompt. + int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR; + + // Launch a second instance of the updater with the runas verb on Windows + // when write access is denied to the installation directory. + HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE; + NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')}; + if (!sUsingService && + (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) { + NS_tchar updateLockFilePath[MAXPATHLEN]; + if (sStagedUpdate) { + // When staging an update, the lock file is: + // \updated.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath); + } else if (sReplaceRequest) { + // When processing a replace request, the lock file is: + // \..\moz_update_in_progress.lock + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH); + *slash = NS_T('\0'); + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s\\moz_update_in_progress.lock"), installDir); + } else { + // In the non-staging update case, the lock file is: + // \.exe.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s.update_in_progress.lock"), argv[callbackIndex]); + } + + // The update_in_progress.lock file should only exist during an update. In + // case it exists attempt to remove it and exit if that fails to prevent + // simultaneous updates occurring. + if (!_waccess(updateLockFilePath, F_OK) && + NS_tremove(updateLockFilePath) != 0) { + // Try to fall back to the old way of doing updates if a staged + // update fails. + if (sStagedUpdate || sReplaceRequest) { + // Note that this could fail, but if it does, there isn't too much we + // can do in order to recover anyways. + WriteStatusFile("pending"); + } + LOG(("Update already in progress! Exiting")); + return 1; + } + + updateLockFileHandle = CreateFileW(updateLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + NS_tsnprintf(elevatedLockFilePath, + sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]), + NS_T("%s/update_elevated.lock"), gPatchDirPath); + + // Even if a file has no sharing access, you can still get its attributes + bool startedFromUnelevatedUpdater = + GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES; + + // If we're running from the service, then we were started with the same + // token as the service so the permissions are already dropped. If we're + // running from an elevated updater that was started from an unelevated + // updater, then we drop the permissions here. We do not drop the + // permissions on the originally called updater because we use its token + // to start the callback application. + if (startedFromUnelevatedUpdater) { + // Disable every privilege we don't need. Processes started using + // CreateProcess will use the same token as this process. + UACHelper::DisablePrivileges(nullptr); + } + + if (updateLockFileHandle == INVALID_HANDLE_VALUE || + (useService && testOnlyFallbackKeyExists && noServiceFallback)) { + if (!_waccess(elevatedLockFilePath, F_OK) && + NS_tremove(elevatedLockFilePath) != 0) { + fprintf(stderr, "Unable to create elevated lock file! Exiting\n"); + return 1; + } + + HANDLE elevatedFileHandle; + elevatedFileHandle = CreateFileW(elevatedLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + if (elevatedFileHandle == INVALID_HANDLE_VALUE) { + LOG(("Unable to create elevated lock file! Exiting")); + return 1; + } + + wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1); + if (!cmdLine) { + CloseHandle(elevatedFileHandle); + return 1; + } + + // Make sure the path to the updater to use for the update is on local. + // We do this check to make sure that file locking is available for + // race condition security checks. + if (useService) { + BOOL isLocal = FALSE; + useService = IsLocalFile(argv[0], isLocal) && isLocal; + } + + // If we have unprompted elevation we should NOT use the service + // for the update. Service updates happen with the SYSTEM account + // which has more privs than we need to update with. + // Windows 8 provides a user interface so users can configure this + // behavior and it can be configured in the registry in all Windows + // versions that support UAC. + if (useService) { + BOOL unpromptedElevation; + if (IsUnpromptedElevation(unpromptedElevation)) { + useService = !unpromptedElevation; + } + } + + // Make sure the service registry entries for the instsallation path + // are available. If not don't use the service. + if (useService) { + WCHAR maintenanceServiceKey[MAX_PATH + 1]; + if (CalculateRegistryPathFromFilePath(gInstallDirPath, + maintenanceServiceKey)) { + HKEY baseKey = nullptr; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, + &baseKey) == ERROR_SUCCESS) { + RegCloseKey(baseKey); + } else { +#ifdef TEST_UPDATER + useService = testOnlyFallbackKeyExists; +#endif + if (!useService) { + lastFallbackError = FALLBACKKEY_NOKEY_ERROR; + } + } + } else { + useService = false; + lastFallbackError = FALLBACKKEY_REGPATH_ERROR; + } + } + + // Originally we used to write "pending" to update.status before + // launching the service command. This is no longer needed now + // since the service command is launched from updater.exe. If anything + // fails in between, we can fall back to using the normal update process + // on our own. + + // If we still want to use the service try to launch the service + // comamnd for the update. + if (useService) { + // If the update couldn't be started, then set useService to false so + // we do the update the old way. + DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv); + useService = (ret == ERROR_SUCCESS); + // If the command was launched then wait for the service to be done. + if (useService) { + bool showProgressUI = false; + // Never show the progress UI when staging updates. + if (!sStagedUpdate) { + // We need to call this separately instead of allowing ShowProgressUI + // to initialize the strings because the service will move the + // ini file out of the way when running updater. + showProgressUI = !InitProgressUIStrings(); + } + + // Wait for the service to stop for 5 seconds. If the service + // has still not stopped then show an indeterminate progress bar. + DWORD lastState = WaitForServiceStop(SVC_NAME, 5); + if (lastState != SERVICE_STOPPED) { + Thread t1; + if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 && + showProgressUI) { + ShowProgressUI(true, false); + } + t1.Join(); + } + + lastState = WaitForServiceStop(SVC_NAME, 1); + if (lastState != SERVICE_STOPPED) { + // If the service doesn't stop after 10 minutes there is + // something seriously wrong. + lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR; + useService = false; + } + } else { + lastFallbackError = FALLBACKKEY_LAUNCH_ERROR; + } + } + + // If the service can't be used when staging an update, make sure that + // the UAC prompt is not shown! In this case, just set the status to + // pending and the update will be applied during the next startup. + if (!useService && sStagedUpdate) { + if (updateLockFileHandle != INVALID_HANDLE_VALUE) { + CloseHandle(updateLockFileHandle); + } + WriteStatusFile("pending"); + return 0; + } + + // If we started the service command, and it finished, check the + // update.status file to make sure it succeeded, and if it did + // we need to manually start the PostUpdate process from the + // current user's session of this unelevated updater.exe the + // current process is running as. + // Note that we don't need to do this if we're just staging the update, + // as the PostUpdate step runs when performing the replacing in that case. + if (useService && !sStagedUpdate) { + bool updateStatusSucceeded = false; + if (IsUpdateStatusSucceeded(updateStatusSucceeded) && + updateStatusSucceeded) { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) { + fprintf(stderr, "The post update process which runs as the user" + " for service update could not be launched."); + } + } + } + + // If we didn't want to use the service at all, or if an update was + // already happening, or launching the service command failed, then + // launch the elevated updater.exe as we do without the service. + // We don't launch the elevated updater in the case that we did have + // write access all along because in that case the only reason we're + // using the service is because we are testing. + if (!useService && !noServiceFallback && + updateLockFileHandle == INVALID_HANDLE_VALUE) { + SHELLEXECUTEINFO sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFO); + sinfo.fMask = SEE_MASK_FLAG_NO_UI | + SEE_MASK_FLAG_DDEWAIT | + SEE_MASK_NOCLOSEPROCESS; + sinfo.hwnd = nullptr; + sinfo.lpFile = argv[0]; + sinfo.lpParameters = cmdLine; + sinfo.lpVerb = L"runas"; + sinfo.nShow = SW_SHOWNORMAL; + + bool result = ShellExecuteEx(&sinfo); + free(cmdLine); + + if (result) { + WaitForSingleObject(sinfo.hProcess, INFINITE); + CloseHandle(sinfo.hProcess); + } else { + WriteStatusFile(ELEVATION_CANCELED); + } + } + + if (argc > callbackIndex) { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + + CloseHandle(elevatedFileHandle); + + if (!useService && !noServiceFallback && + INVALID_HANDLE_VALUE == updateLockFileHandle) { + // We didn't use the service and we did run the elevated updater.exe. + // The elevated updater.exe is responsible for writing out the + // update.status file. + return 0; + } else if (useService) { + // The service command was launched. The service is responsible for + // writing out the update.status file. + if (updateLockFileHandle != INVALID_HANDLE_VALUE) { + CloseHandle(updateLockFileHandle); + } + return 0; + } else { + // Otherwise the service command was not launched at all. + // We are only reaching this code path because we had write access + // all along to the directory and a fallback key existed, and we + // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists). + // We only currently use this env var from XPCShell tests. + CloseHandle(updateLockFileHandle); + WriteStatusFile(lastFallbackError); + return 0; + } + } + } +#endif + +#if defined(MOZ_WIDGET_GONK) + // In gonk, the master b2g process sets its umask to 0027 because + // there's no reason for it to ever create world-readable files. + // The updater binary, however, needs to do this, and it inherits + // the master process's cautious umask. So we drop down a bit here. + umask(0022); + + // Remount the /system partition as read-write for gonk. The destructor will + // remount /system as read-only. We add an extra level of scope here to avoid + // calling LogFinish() before the GonkAutoMounter destructor has a chance + // to be called + { +#if !defined(TEST_UPDATER) + GonkAutoMounter mounter; + if (mounter.GetAccess() != MountAccess::ReadWrite) { + WriteStatusFile(FILESYSTEM_MOUNT_READWRITE_ERROR); + return 1; + } +#endif +#endif + + if (sStagedUpdate) { + // When staging updates, blow away the old installation directory and create + // it from scratch. + ensure_remove_recursive(gWorkingDirPath); + } + if (!sReplaceRequest) { + // Try to create the destination directory if it doesn't exist + int rv = NS_tmkdir(gWorkingDirPath, 0755); + if (rv != OK && errno != EEXIST) { +#ifdef XP_MACOSX + if (isElevated) { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + } + +#ifdef XP_WIN + // For replace requests, we don't need to do any real updates, so this is not + // necessary. + if (!sReplaceRequest) { + // Allocate enough space for the length of the path an optional additional + // trailing slash and null termination. + NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar)); + if (!destpath) { + return 1; + } + + NS_tchar *c = destpath; + NS_tstrcpy(c, gWorkingDirPath); + c += NS_tstrlen(gWorkingDirPath); + if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') && + gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\')) { + NS_tstrcat(c, NS_T("/")); + c += NS_tstrlen(NS_T("/")); + } + *c = NS_T('\0'); + c++; + + gDestPath = destpath; + } + + NS_tchar applyDirLongPath[MAXPATHLEN]; + if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath, + sizeof(applyDirLongPath) / sizeof(applyDirLongPath[0]))) { + LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + return 1; + } + + HANDLE callbackFile = INVALID_HANDLE_VALUE; + if (argc > callbackIndex) { + // If the callback executable is specified it must exist for a successful + // update. It is important we null out the whole buffer here because later + // we make the assumption that the callback application is inside the + // apply-to dir. If we don't have a fully null'ed out buffer it can lead + // to stack corruption which causes crashes and other problems. + NS_tchar callbackLongPath[MAXPATHLEN]; + ZeroMemory(callbackLongPath, sizeof(callbackLongPath)); + NS_tchar *targetPath = argv[callbackIndex]; + NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') }; + size_t bufferLeft = MAXPATHLEN * 2; + if (sReplaceRequest) { + // In case of replace requests, we should look for the callback file in + // the destination directory. + size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex], + gInstallDirPath, + nullptr); + NS_tchar *p = buffer; + NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength); + p += commonPrefixLength; + bufferLeft -= commonPrefixLength; + NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft); + + size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength); + p += len; + bufferLeft -= len; + *p = NS_T('\\'); + ++p; + bufferLeft--; + *p = NS_T('\0'); + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex], + installDir, + nullptr); + NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength, + commonPrefixLength), bufferLeft); + targetPath = buffer; + } + if (!GetLongPathNameW(targetPath, callbackLongPath, + sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) { + LOG(("NS_main: unable to find callback file: " LOG_S, targetPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_CALLBACK_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) { + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + } + return 1; + } + + // Doing this is only necessary when we're actually applying a patch. + if (!sReplaceRequest) { + int len = NS_tstrlen(applyDirLongPath); + NS_tchar *s = callbackLongPath; + NS_tchar *d = gCallbackRelPath; + // advance to the apply to directory and advance past the trailing backslash + // if present. + s += len; + if (*s == NS_T('\\')) + ++s; + + // Copy the string and replace backslashes with forward slashes along the + // way. + do { + if (*s == NS_T('\\')) + *d = NS_T('/'); + else + *d = *s; + ++s; + ++d; + } while (*s); + *d = NS_T('\0'); + ++d; + + const size_t callbackBackupPathBufSize = + sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]); + const int callbackBackupPathLen = + NS_tsnprintf(gCallbackBackupPath, callbackBackupPathBufSize, + NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]); + + if (callbackBackupPathLen < 0 || + callbackBackupPathLen >= static_cast(callbackBackupPathBufSize)) { + LOG(("NS_main: callback backup path truncated")); + LogFinish(); + WriteStatusFile(USAGE_ERROR); + + // Don't attempt to launch the callback when the callback path is + // longer than expected. + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + return 1; + } + + // Make a copy of the callback executable so it can be read when patching. + NS_tremove(gCallbackBackupPath); + if(!CopyFileW(argv[callbackIndex], gCallbackBackupPath, true)) { + DWORD copyFileError = GetLastError(); + LOG(("NS_main: failed to copy callback file " LOG_S + " into place at " LOG_S, argv[callbackIndex], gCallbackBackupPath)); + LogFinish(); + if (copyFileError == ERROR_ACCESS_DENIED) { + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); + } else { + WriteStatusFile(WRITE_ERROR_CALLBACK_APP); + } + + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + LaunchCallbackApp(argv[callbackIndex], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + return 1; + } + + // Since the process may be signaled as exited by WaitForSingleObject before + // the release of the executable image try to lock the main executable file + // multiple times before giving up. If we end up giving up, we won't + // fail the update. + const int max_retries = 10; + int retries = 1; + DWORD lastWriteError = 0; + do { + // By opening a file handle wihout FILE_SHARE_READ to the callback + // executable, the OS will prevent launching the process while it is + // being updated. + callbackFile = CreateFileW(targetPath, + DELETE | GENERIC_WRITE, + // allow delete, rename, and write + FILE_SHARE_DELETE | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, 0, nullptr); + if (callbackFile != INVALID_HANDLE_VALUE) + break; + + lastWriteError = GetLastError(); + LOG(("NS_main: callback app file open attempt %d failed. " \ + "File: " LOG_S ". Last error: %d", retries, + targetPath, lastWriteError)); + + Sleep(100); + } while (++retries <= max_retries); + + // CreateFileW will fail if the callback executable is already in use. + if (callbackFile == INVALID_HANDLE_VALUE) { + // Only fail the update if the last error was not a sharing violation. + if (lastWriteError != ERROR_SHARING_VIOLATION) { + LOG(("NS_main: callback app file in use, failed to exclusively open " \ + "executable file: " LOG_S, argv[callbackIndex])); + LogFinish(); + if (lastWriteError == ERROR_ACCESS_DENIED) { + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); + } else { + WriteStatusFile(WRITE_ERROR_CALLBACK_APP); + } + + NS_tremove(gCallbackBackupPath); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + return 1; + } + LOG(("NS_main: callback app file in use, continuing without " \ + "exclusive access for executable file: " LOG_S, + argv[callbackIndex])); + } + } + } + + // DELETE_DIR is not required when performing a staged update or replace + // request; it can be used during a replace request but then it doesn't + // use gDeleteDirPath. + if (!sStagedUpdate && !sReplaceRequest) { + // The directory to move files that are in use to on Windows. This directory + // will be deleted after the update is finished, on OS reboot using + // MoveFileEx if it contains files that are in use, or by the post update + // process after the update finishes. On Windows when performing a normal + // update (e.g. the update is not a staged update and is not a replace + // request) gWorkingDirPath is the same as gInstallDirPath and + // gWorkingDirPath is used because it is the destination directory. + NS_tsnprintf(gDeleteDirPath, + sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]), + NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR); + + if (NS_taccess(gDeleteDirPath, F_OK)) { + NS_tmkdir(gDeleteDirPath, 0755); + } + } +#endif /* XP_WIN */ + + // Run update process on a background thread. ShowProgressUI may return + // before QuitProgressUI has been called, so wait for UpdateThreadFunc to + // terminate. Avoid showing the progress UI when staging an update, or if this + // is an elevated process on OSX. + Thread t; + if (t.Run(UpdateThreadFunc, nullptr) == 0) { + if (!sStagedUpdate && !sReplaceRequest +#ifdef XP_MACOSX + && !isElevated +#endif + ) { + ShowProgressUI(); + } + } + t.Join(); + +#ifdef XP_WIN + if (argc > callbackIndex && !sReplaceRequest) { + if (callbackFile != INVALID_HANDLE_VALUE) { + CloseHandle(callbackFile); + } + // Remove the copy of the callback executable. + NS_tremove(gCallbackBackupPath); + } + + if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) { + LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", + DELETE_DIR, errno)); + // The directory probably couldn't be removed due to it containing files + // that are in use and will be removed on OS reboot. The call to remove the + // directory on OS reboot is done after the calls to remove the files so the + // files are removed first on OS reboot since the directory must be empty + // for the directory removal to be successful. The MoveFileEx call to remove + // the directory on OS reboot will fail if the process doesn't have write + // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the + // installer / uninstaller will delete the directory along with its contents + // after an update is applied, on reinstall, and on uninstall. + if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { + LOG(("NS_main: directory will be removed on OS reboot: " LOG_S, + DELETE_DIR)); + } else { + LOG(("NS_main: failed to schedule OS reboot removal of " \ + "directory: " LOG_S, DELETE_DIR)); + } + } +#endif /* XP_WIN */ + +#if defined(MOZ_WIDGET_GONK) + } // end the extra level of scope for the GonkAutoMounter +#endif + +#ifdef XP_MACOSX + // When the update is successful remove the precomplete file in the root of + // the application bundle and move the distribution directory from + // Contents/MacOS to Contents/Resources and if both exist delete the + // directory under Contents/MacOS (see Bug 1068439). + if (gSucceeded && !sStagedUpdate) { + NS_tchar oldPrecomplete[MAXPATHLEN]; + NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]), + NS_T("%s/precomplete"), gInstallDirPath); + NS_tremove(oldPrecomplete); + + NS_tchar oldDistDir[MAXPATHLEN]; + NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]), + NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath); + int rv = NS_taccess(oldDistDir, F_OK); + if (!rv) { + NS_tchar newDistDir[MAXPATHLEN]; + NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]), + NS_T("%s/Contents/Resources/distribution"), gInstallDirPath); + rv = NS_taccess(newDistDir, F_OK); + if (!rv) { + LOG(("New distribution directory already exists... removing old " \ + "distribution directory: " LOG_S, oldDistDir)); + rv = ensure_remove_recursive(oldDistDir); + if (rv) { + LOG(("Removing old distribution directory failed - err: %d", rv)); + } + } else { + LOG(("Moving old distribution directory to new location. src: " LOG_S \ + ", dst:" LOG_S, oldDistDir, newDistDir)); + rv = rename_file(oldDistDir, newDistDir, true); + if (rv) { + LOG(("Moving old distribution directory to new location failed - " \ + "err: %d", rv)); + } + } + } + } + + if (isElevated) { + SetGroupOwnershipAndPermissions(gInstallDirPath); + freeArguments(argc, argv); + CleanupElevatedMacUpdate(false); + } else if (IsOwnedByGroupAdmin(gInstallDirPath)) { + // If the group ownership of the Firefox .app bundle was set to the "admin" + // group during a previous elevated update, we need to ensure that all files + // in the bundle have group ownership of "admin" as well as write permission + // for the group to not break updates in the future. + SetGroupOwnershipAndPermissions(gInstallDirPath); + } +#endif /* XP_MACOSX */ + + LogFinish(); + + int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex +#ifdef XP_WIN + , elevatedLockFilePath + , updateLockFileHandle +#elif XP_MACOSX + , isElevated +#endif + ); + + return retVal ? retVal : (gSucceeded ? 0 : 1); +} + +class ActionList +{ +public: + ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { } + ~ActionList(); + + void Append(Action* action); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + Action *mFirst; + Action *mLast; + int mCount; +}; + +ActionList::~ActionList() +{ + Action* a = mFirst; + while (a) { + Action *b = a; + a = a->mNext; + delete b; + } +} + +void +ActionList::Append(Action *action) +{ + if (mLast) + mLast->mNext = action; + else + mFirst = action; + + mLast = action; + mCount++; +} + +int +ActionList::Prepare() +{ + // If the action list is empty then we should fail in order to signal that + // something has gone wrong. Otherwise we report success when nothing is + // actually done. See bug 327140. + if (mCount == 0) { + LOG(("empty action list")); + return MAR_ERROR_EMPTY_ACTION_LIST; + } + + Action *a = mFirst; + int i = 0; + while (a) { + int rv = a->Prepare(); + if (rv) + return rv; + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +int +ActionList::Execute() +{ + int currentProgress = 0, maxProgress = 0; + Action *a = mFirst; + while (a) { + maxProgress += a->mProgressCost; + a = a->mNext; + } + + a = mFirst; + while (a) { + int rv = a->Execute(); + if (rv) { + LOG(("### execution failed")); + return rv; + } + + currentProgress += a->mProgressCost; + float percent = float(currentProgress) / float(maxProgress); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +void +ActionList::Finish(int status) +{ + Action *a = mFirst; + int i = 0; + while (a) { + a->Finish(status); + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE + + PROGRESS_FINISH_SIZE * percent); + + a = a->mNext; + } + + if (status == OK) + gSucceeded = true; +} + + +#ifdef XP_WIN +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + WIN32_FIND_DATAW finddata; + HANDLE hFindFile; + NS_tchar searchspec[MAXPATHLEN]; + NS_tchar foundpath[MAXPATHLEN]; + + NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]), + NS_T("%s*"), dirpath); + mozilla::UniquePtr pszSpec(get_full_path(searchspec)); + + hFindFile = FindFirstFileW(pszSpec.get(), &finddata); + if (hFindFile != INVALID_HANDLE_VALUE) { + do { + // Don't process the current or parent directory. + if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 || + NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), dirpath, finddata.cFileName); + if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + return rv; + } + } else { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(foundpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + return rv; + } + free(quotedpath); + + list->Append(action); + } + } while (FindNextFileW(hFindFile, &finddata) != 0); + + FindClose(hFindFile); + { + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(dirpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + } else { + list->Append(action); + } + free(quotedpath); + } + } + + return rv; +} + +#elif defined(SOLARIS) +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + NS_tchar foundpath[MAXPATHLEN]; + struct { + dirent dent_buffer; + char chars[MAXNAMLEN]; + } ent_buf; + struct dirent* ent; + mozilla::UniquePtr searchpath(get_full_path(dirpath)); + + DIR* dir = opendir(searchpath.get()); + if (!dir) { + LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(), + errno)); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + + while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) { + if ((strcmp(ent->d_name, ".") == 0) || + (strcmp(ent->d_name, "..") == 0)) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), searchpath.get(), ent->d_name); + struct stat64 st_buf; + int test = stat64(foundpath, &st_buf); + if (test) { + closedir(dir); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + if (S_ISDIR(st_buf.st_mode)) { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + closedir(dir); + return rv; + } + } else { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_path(foundpath)); + if (!quotedpath) { + closedir(dir); + return PARSE_ERROR; + } + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + closedir(dir); + return rv; + } + + list->Append(action); + } + } + closedir(dir); + + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_path(dirpath)); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + } + else { + list->Append(action); + } + + return rv; +} + +#else + +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + FTS *ftsdir; + FTSENT *ftsdirEntry; + mozilla::UniquePtr searchpath(get_full_path(dirpath)); + + // Remove the trailing slash so the paths don't contain double slashes. The + // existence of the slash has already been checked in DoUpdate. + searchpath[NS_tstrlen(searchpath.get()) - 1] = NS_T('\0'); + char* const pathargv[] = {searchpath.get(), nullptr}; + + // FTS_NOCHDIR is used so relative paths from the destination directory are + // returned. + if (!(ftsdir = fts_open(pathargv, + FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR, + nullptr))) + return UNEXPECTED_FILE_OPERATION_ERROR; + + while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) { + NS_tchar foundpath[MAXPATHLEN]; + NS_tchar *quotedpath = nullptr; + Action *action = nullptr; + + switch (ftsdirEntry->fts_info) { + // Filesystem objects that shouldn't be in the application's directories + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + LOG(("add_dir_entries: found a non-standard file: " LOG_S, + ftsdirEntry->fts_path)); + // Fall through and try to remove as a file + MOZ_FALLTHROUGH; + + // Files + case FTS_F: + case FTS_NSOK: + // Add the file to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(get_relative_path(foundpath)); + if (!quotedpath) { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + action = new RemoveFile(); + rv = action->Parse(quotedpath); + free(quotedpath); + if (!rv) + list->Append(action); + break; + + // Directories + case FTS_DP: + rv = OK; + // Add the directory to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(get_relative_path(foundpath)); + if (!quotedpath) { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + + action = new RemoveDir(); + rv = action->Parse(quotedpath); + free(quotedpath); + if (!rv) + list->Append(action); + break; + + // Errors + case FTS_DNR: + case FTS_NS: + // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that + // we're racing with ourselves. Though strange, the entry will be + // removed anyway. + if (ENOENT == ftsdirEntry->fts_errno) { + rv = OK; + break; + } + MOZ_FALLTHROUGH; + + case FTS_ERR: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d", + ftsdirEntry->fts_path, ftsdirEntry->fts_errno)); + break; + + case FTS_DC: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S, + ftsdirEntry->fts_path)); + break; + + default: + // FTS_D is ignored and FTS_DP is used instead (post-order). + rv = OK; + break; + } + + if (rv != OK) + break; + } + + fts_close(ftsdir); + + return rv; +} +#endif + +static NS_tchar* +GetManifestContents(const NS_tchar *manifest) +{ + AutoFile mfile(NS_tfopen(manifest, NS_T("rb"))); + if (mfile == nullptr) { + LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest)); + return nullptr; + } + + struct stat ms; + int rv = fstat(fileno((FILE *)mfile), &ms); + if (rv) { + LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest)); + return nullptr; + } + + char *mbuf = (char *) malloc(ms.st_size + 1); + if (!mbuf) + return nullptr; + + size_t r = ms.st_size; + char *rb = mbuf; + while (r) { + const size_t count = mmin(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, mfile); + if (c != count) { + LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest)); + free(mbuf); + return nullptr; + } + + r -= c; + rb += c; + } + mbuf[ms.st_size] = '\0'; + rb = mbuf; + +#ifndef XP_WIN + return rb; +#else + NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar)); + if (!wrb) { + free(mbuf); + return nullptr; + } + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb, + ms.st_size + 1)) { + LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError())); + free(mbuf); + free(wrb); + return nullptr; + } + free(mbuf); + + return wrb; +#endif +} + +int AddPreCompleteActions(ActionList *list) +{ + if (sIsOSUpdate) { + return OK; + } + +#ifdef XP_MACOSX + mozilla::UniquePtr manifestPath(get_full_path( + NS_T("Contents/Resources/precomplete"))); +#else + mozilla::UniquePtr manifestPath(get_full_path( + NS_T("precomplete"))); +#endif + + NS_tchar *rb = GetManifestContents(manifestPath.get()); + if (rb == nullptr) { + LOG(("AddPreCompleteActions: error getting contents of precomplete " \ + "manifest")); + // Applications aren't required to have a precomplete manifest. The mar + // generation scripts enforce the presence of a precomplete manifest. + return OK; + } + + int rv; + NS_tchar *line; + while((line = mstrtok(kNL, &rb)) != 0) { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) { + LOG(("AddPreCompleteActions: token not found in manifest")); + return PARSE_ERROR; + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) { // no longer supported + continue; + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty + action = new RemoveDir(); + } + else { + LOG(("AddPreCompleteActions: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list->Append(action); + } + + return OK; +} + +int DoUpdate() +{ + NS_tchar manifest[MAXPATHLEN]; + NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]), + NS_T("%s/updating/update.manifest"), gWorkingDirPath); + ensure_parent_dir(manifest); + + // extract the manifest + int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest); + if (rv) { + rv = gArchiveReader.ExtractFile("updatev2.manifest", manifest); + if (rv) { + LOG(("DoUpdate: error extracting manifest file")); + return rv; + } + } + + NS_tchar *rb = GetManifestContents(manifest); + NS_tremove(manifest); + if (rb == nullptr) { + LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest)); + return READ_ERROR; + } + + + ActionList list; + NS_tchar *line; + bool isFirstAction = true; + + while((line = mstrtok(kNL, &rb)) != 0) { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) { + LOG(("DoUpdate: token not found in manifest")); + return PARSE_ERROR; + } + + if (isFirstAction) { + isFirstAction = false; + // The update manifest isn't required to have a type declaration. The mar + // generation scripts enforce the presence of the type declaration. + if (NS_tstrcmp(token, NS_T("type")) == 0) { + const NS_tchar *type = mstrtok(kQuote, &line); + LOG(("UPDATE TYPE " LOG_S, type)); + if (NS_tstrcmp(type, NS_T("complete")) == 0) { + rv = AddPreCompleteActions(&list); + if (rv) + return rv; + } + continue; + } + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty + action = new RemoveDir(); + } + else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) { // rmdir recursive + const NS_tchar *reldirpath = mstrtok(kQuote, &line); + if (!reldirpath) + return PARSE_ERROR; + + if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/')) + return PARSE_ERROR; + + rv = add_dir_entries(reldirpath, &list); + if (rv) + return rv; + + continue; + } + else if (NS_tstrcmp(token, NS_T("add")) == 0) { + action = new AddFile(); + } + else if (NS_tstrcmp(token, NS_T("patch")) == 0) { + action = new PatchFile(); + } + else if (NS_tstrcmp(token, NS_T("add-if")) == 0) { // Add if exists + action = new AddIfFile(); + } + else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) { // Add if not exists + action = new AddIfNotFile(); + } + else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists + action = new PatchIfFile(); + } + else { + LOG(("DoUpdate: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list.Append(action); + } + + rv = list.Prepare(); + if (rv) + return rv; + + rv = list.Execute(); + + list.Finish(rv); + return rv; +} diff --git a/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest b/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest new file mode 100644 index 000000000..9a6cdb565 --- /dev/null +++ b/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest @@ -0,0 +1,38 @@ + + + +Updater + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/update/updater/updater.exe.manifest b/toolkit/mozapps/update/updater/updater.exe.manifest new file mode 100644 index 000000000..cd229c954 --- /dev/null +++ b/toolkit/mozapps/update/updater/updater.exe.manifest @@ -0,0 +1,26 @@ + + + +Updater + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/update/updater/updater.ico b/toolkit/mozapps/update/updater/updater.ico new file mode 100644 index 000000000..48457029d Binary files /dev/null and b/toolkit/mozapps/update/updater/updater.ico differ diff --git a/toolkit/mozapps/update/updater/updater.png b/toolkit/mozapps/update/updater/updater.png new file mode 100644 index 000000000..7b5e78907 Binary files /dev/null and b/toolkit/mozapps/update/updater/updater.png differ diff --git a/toolkit/mozapps/update/updater/updater.rc b/toolkit/mozapps/update/updater/updater.rc new file mode 100644 index 000000000..7603eecb6 --- /dev/null +++ b/toolkit/mozapps/update/updater/updater.rc @@ -0,0 +1,137 @@ +/* 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/. */ + +// Microsoft Visual C++ generated resource script. +// +#ifdef TEST_UPDATER +#include "../resource.h" +#define MANIFEST_PATH "../updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "../updater.exe.comctl32.manifest" +#define ICON_PATH "../updater.ico" +#else +#include "resource.h" +#define MANIFEST_PATH "updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "updater.exe.comctl32.manifest" +#define ICON_PATH "updater.ico" +#endif + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winresrc.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +1 RT_MANIFEST MANIFEST_PATH +IDR_COMCTL32_MANIFEST RT_MANIFEST COMCTL32_MANIFEST_PATH + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +IDI_DIALOG ICON ICON_PATH + + +///////////////////////////////////////////////////////////////////////////// +// +// Embedded an identifier to uniquely identiy this as a Mozilla updater. +// + +STRINGTABLE +{ + IDS_UPDATER_IDENTITY, "moz-updater.exe-4cdccec4-5ee0-4a06-9817-4cd899a9db49" +} + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG DIALOGEX 0, 0, 253, 41 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,24,239,10 + LTEXT "",IDC_INFO,7,8,239,13,SS_NOPREFIX +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 246 + TOPMARGIN, 7 + BOTTOMMARGIN, 39 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winresrc.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/toolkit/mozapps/update/updater/win_dirent.cpp b/toolkit/mozapps/update/updater/win_dirent.cpp new file mode 100644 index 000000000..b0807ba5e --- /dev/null +++ b/toolkit/mozapps/update/updater/win_dirent.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "win_dirent.h" +#include +#include + +// This file implements the minimum set of dirent APIs used by updater.cpp on +// Windows. If updater.cpp is modified to use more of this API, we need to +// implement those parts here too. + +static dirent gDirEnt; + +DIR::DIR(const WCHAR* path) + : findHandle(INVALID_HANDLE_VALUE) +{ + memset(name, 0, sizeof(name)); + wcsncpy(name, path, sizeof(name)/sizeof(name[0])); + wcsncat(name, L"\\*", sizeof(name)/sizeof(name[0]) - wcslen(name) - 1); +} + +DIR::~DIR() +{ + if (findHandle != INVALID_HANDLE_VALUE) { + FindClose(findHandle); + } +} + +dirent::dirent() +{ + d_name[0] = L'\0'; +} + +DIR* +opendir(const WCHAR* path) +{ + return new DIR(path); +} + +int +closedir(DIR* dir) +{ + delete dir; + return 0; +} + +dirent* readdir(DIR* dir) +{ + WIN32_FIND_DATAW data; + if (dir->findHandle != INVALID_HANDLE_VALUE) { + BOOL result = FindNextFileW(dir->findHandle, &data); + if (!result) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) { + errno = ENOENT; + } + return 0; + } + } else { + // Reading the first directory entry + dir->findHandle = FindFirstFileW(dir->name, &data); + if (dir->findHandle == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + errno = ENOENT; + } else { + errno = EBADF; + } + return 0; + } + } + memset(gDirEnt.d_name, 0, sizeof(gDirEnt.d_name)); + wcsncpy(gDirEnt.d_name, data.cFileName, + sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0])); + return &gDirEnt; +} + diff --git a/toolkit/mozapps/update/updater/xpcshellCertificate.der b/toolkit/mozapps/update/updater/xpcshellCertificate.der new file mode 100644 index 000000000..185b2dff4 Binary files /dev/null and b/toolkit/mozapps/update/updater/xpcshellCertificate.der differ -- cgit v1.2.3