From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- .../mozapps/extensions/test/xpcshell/.eslintrc.js | 24 + .../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 .../xpcshell/data/mlbf-blocked1-unblocked2.bin | Bin 0 -> 32 bytes .../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 + .../data/productaddons/content_signing_aus_ee.pem | 15 + .../content_signing_aus_ee.pem.certspec | 5 + .../data/productaddons/content_signing_int.pem | 18 + .../productaddons/content_signing_int.pem.certspec | 4 + .../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/langpack_signed.xpi | Bin 0 -> 4452 bytes .../data/signing_checks/langpack_unsigned.xpi | Bin 0 -> 413 bytes .../test/xpcshell/data/signing_checks/long.xpi | Bin 0 -> 4761 bytes .../xpcshell/data/signing_checks/privileged.xpi | Bin 0 -> 4659 bytes .../test/xpcshell/data/signing_checks/signed1.xpi | Bin 0 -> 4702 bytes .../test/xpcshell/data/signing_checks/signed2.xpi | Bin 0 -> 4697 bytes .../test/xpcshell/data/signing_checks/unsigned.xpi | Bin 0 -> 528 bytes .../xpcshell/data/test_AddonRepository_cache.json | 134 ++ .../xpcshell/data/test_AddonRepository_empty.json | 7 + .../xpcshell/data/test_AddonRepository_fail.json | 1 + .../data/test_AddonRepository_getAddonsByIDs.json | 116 ++ .../test/xpcshell/data/test_backgroundupdate.json | 46 + .../data/test_blocklist_metadata_filters_1.xml | 21 + .../test/xpcshell/data/test_blocklist_prefs_1.xml | 28 + .../test/xpcshell/data/test_bug393285.xml | 30 + .../data/test_bug449027_app-extensions.json | 332 +++++ .../xpcshell/data/test_bug449027_app-plugins.json | 332 +++++ .../test/xpcshell/data/test_bug449027_app.xml | 333 +++++ .../data/test_bug449027_toolkit-extensions.json | 189 +++ .../data/test_bug449027_toolkit-plugins.json | 189 +++ .../test/xpcshell/data/test_bug449027_toolkit.xml | 208 +++ .../test/xpcshell/data/test_bug468528.xml | 15 + .../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_bug655254.json | 17 + .../test/xpcshell/data/test_corrupt.json | 30 + .../xpcshell/data/test_delay_updates_complete.json | 12 + .../data/test_delay_updates_complete_legacy.json | 18 + .../xpcshell/data/test_delay_updates_defer.json | 12 + .../data/test_delay_updates_defer_legacy.json | 18 + .../xpcshell/data/test_delay_updates_ignore.json | 12 + .../data/test_delay_updates_ignore_legacy.json | 18 + .../xpcshell/data/test_delay_updates_staged.json | 32 + .../test/xpcshell/data/test_gfxBlacklist.json | 377 ++++++ .../xpcshell/data/test_gfxBlacklist_AllOS.json | 581 ++++++++ .../xpcshell/data/test_gfxBlacklist_OSVersion.json | 20 + .../test/xpcshell/data/test_install_addons.json | 31 + .../test/xpcshell/data/test_install_compat.json | 27 + .../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_softblocked1.xml | 9 + .../xpcshell/data/test_trash_directory.worker.js | 42 + .../extensions/test/xpcshell/data/test_update.json | 137 ++ .../test/xpcshell/data/test_update_addons.json | 14 + .../test/xpcshell/data/test_update_compat.json | 28 + .../test/xpcshell/data/test_updatecheck.json | 269 ++++ .../extensions/test/xpcshell/data/unsigned.xpi | Bin 0 -> 463 bytes .../test/xpcshell/data/webext-implicit-id.xpi | Bin 0 -> 4182 bytes .../extensions/test/xpcshell/head_addons.js | 1226 +++++++++++++++++ .../test/xpcshell/head_amremotesettings.js | 31 + .../extensions/test/xpcshell/head_cert_handling.js | 33 + .../extensions/test/xpcshell/head_compat.js | 49 + .../extensions/test/xpcshell/head_sideload.js | 76 ++ .../extensions/test/xpcshell/head_system_addons.js | 472 +++++++ .../extensions/test/xpcshell/head_unpack.js | 3 + .../extensions/test/xpcshell/rs-blocklist/head.js | 120 ++ .../rs-blocklist/test_android_blocklist_dump.js | 120 ++ .../rs-blocklist/test_blocklist_addonBlockURL.js | 56 + .../rs-blocklist/test_blocklist_appversion.js | 293 ++++ .../rs-blocklist/test_blocklist_clients.js | 228 ++++ .../xpcshell/rs-blocklist/test_blocklist_gfx.js | 113 ++ .../test_blocklist_metadata_filters.js | 116 ++ .../xpcshell/rs-blocklist/test_blocklist_mlbf.js | 267 ++++ .../rs-blocklist/test_blocklist_mlbf_dump.js | 156 +++ .../rs-blocklist/test_blocklist_mlbf_fetch.js | 231 ++++ .../rs-blocklist/test_blocklist_mlbf_stashes.js | 219 +++ .../rs-blocklist/test_blocklist_mlbf_telemetry.js | 188 +++ .../rs-blocklist/test_blocklist_mlbf_update.js | 75 ++ .../xpcshell/rs-blocklist/test_blocklist_osabi.js | 286 ++++ .../xpcshell/rs-blocklist/test_blocklist_prefs.js | 106 ++ .../rs-blocklist/test_blocklist_regexp_split.js | 225 ++++ .../rs-blocklist/test_blocklist_severities.js | 504 +++++++ .../test_blocklist_statechange_telemetry.js | 411 ++++++ .../test_blocklist_targetapp_filter.js | 392 ++++++ .../rs-blocklist/test_blocklist_telemetry.js | 138 ++ .../xpcshell/rs-blocklist/test_blocklistchange.js | 1410 ++++++++++++++++++++ .../rs-blocklist/test_blocklistchange_v2.js | 13 + .../rs-blocklist/test_gfxBlacklist_Device.js | 73 + .../rs-blocklist/test_gfxBlacklist_DriverNew.js | 67 + .../test_gfxBlacklist_Equal_DriverNew.js | 112 ++ .../test_gfxBlacklist_Equal_DriverOld.js | 68 + .../rs-blocklist/test_gfxBlacklist_Equal_OK.js | 68 + .../test_gfxBlacklist_GTE_DriverOld.js | 68 + .../rs-blocklist/test_gfxBlacklist_GTE_OK.js | 70 + .../test_gfxBlacklist_No_Comparison.js | 69 + .../xpcshell/rs-blocklist/test_gfxBlacklist_OK.js | 69 + .../xpcshell/rs-blocklist/test_gfxBlacklist_OS.js | 68 + .../test_gfxBlacklist_OSVersion_match.js | 70 + ...fxBlacklist_OSVersion_mismatch_DriverVersion.js | 70 + ...st_gfxBlacklist_OSVersion_mismatch_OSVersion.js | 71 + .../rs-blocklist/test_gfxBlacklist_Vendor.js | 68 + .../rs-blocklist/test_gfxBlacklist_Version.js | 190 +++ .../rs-blocklist/test_gfxBlacklist_prefs.js | 124 ++ .../test/xpcshell/rs-blocklist/test_softblocked.js | 61 + .../test/xpcshell/rs-blocklist/xpcshell.ini | 67 + .../extensions/test/xpcshell/test_AbuseReporter.js | 904 +++++++++++++ .../test/xpcshell/test_AddonRepository.js | 316 +++++ .../test/xpcshell/test_AddonRepository_cache.js | 714 ++++++++++ .../xpcshell/test_AddonRepository_cache_locale.js | 217 +++ .../xpcshell/test_AddonRepository_langpacks.js | 135 ++ .../test/xpcshell/test_AddonRepository_paging.js | 91 ++ .../test/xpcshell/test_ProductAddonChecker.js | 292 ++++ .../test_ProductAddonChecker_signatures.js | 201 +++ .../test_QuarantinedDomains_AMRemoteSettings.js | 79 ++ .../extensions/test/xpcshell/test_XPIStates.js | 133 ++ .../extensions/test/xpcshell/test_XPIcancel.js | 70 + .../extensions/test/xpcshell/test_addonStartup.js | 93 ++ .../test_addon_manager_telemetry_events.js | 875 ++++++++++++ .../test/xpcshell/test_amo_stats_telemetry.js | 102 ++ .../extensions/test/xpcshell/test_aom_startup.js | 189 +++ .../extensions/test/xpcshell/test_bad_json.js | 41 + .../extensions/test/xpcshell/test_badschema.js | 237 ++++ .../extensions/test/xpcshell/test_bug587088.js | 194 +++ .../test/xpcshell/test_builtin_location.js | 149 +++ .../extensions/test/xpcshell/test_cacheflush.js | 86 ++ .../extensions/test/xpcshell/test_childprocess.js | 25 + .../test_colorways_builtin_theme_upgrades.js | 582 ++++++++ .../extensions/test/xpcshell/test_cookies.js | 102 ++ .../extensions/test/xpcshell/test_corrupt.js | 216 +++ .../test/xpcshell/test_crash_annotation_quoting.js | 25 + .../extensions/test/xpcshell/test_db_path.js | 64 + .../xpcshell/test_delay_update_webextension.js | 556 ++++++++ .../extensions/test/xpcshell/test_dependencies.js | 140 ++ .../test/xpcshell/test_dictionary_webextension.js | 233 ++++ .../extensions/test/xpcshell/test_distribution.js | 115 ++ .../test/xpcshell/test_distribution_langpack.js | 112 ++ .../test/xpcshell/test_embedderDisabled.js | 124 ++ .../mozapps/extensions/test/xpcshell/test_error.js | 75 ++ .../test/xpcshell/test_ext_management.js | 223 ++++ .../extensions/test/xpcshell/test_filepointer.js | 327 +++++ .../extensions/test/xpcshell/test_general.js | 49 + .../test/xpcshell/test_getInstallSourceFromHost.js | 47 + .../extensions/test/xpcshell/test_gmpProvider.js | 457 +++++++ .../extensions/test/xpcshell/test_harness.js | 13 + .../extensions/test/xpcshell/test_hidden.js | 251 ++++ .../extensions/test/xpcshell/test_install.js | 1050 +++++++++++++++ .../test/xpcshell/test_installOrigins.js | 535 ++++++++ .../test/xpcshell/test_install_cancel.js | 92 ++ .../test/xpcshell/test_install_file_change.js | 180 +++ .../extensions/test/xpcshell/test_install_icons.js | 62 + .../xpcshell/test_installtrigger_deprecation.js | 346 +++++ .../test/xpcshell/test_installtrigger_schemes.js | 75 ++ .../extensions/test/xpcshell/test_isDebuggable.js | 21 + .../extensions/test/xpcshell/test_isReady.js | 71 + .../xpcshell/test_loadManifest_isPrivileged.js | 229 ++++ .../extensions/test/xpcshell/test_locale.js | 103 ++ .../test/xpcshell/test_moved_extension_metadata.js | 186 +++ .../extensions/test/xpcshell/test_no_addons.js | 79 ++ .../test/xpcshell/test_nodisable_hidden.js | 99 ++ .../xpcshell/test_onPropertyChanged_appDisabled.js | 52 + .../extensions/test/xpcshell/test_permissions.js | 199 +++ .../test/xpcshell/test_permissions_prefs.js | 99 ++ .../test/xpcshell/test_pref_properties.js | 221 +++ .../test/xpcshell/test_provider_markSafe.js | 43 + .../test/xpcshell/test_provider_shutdown.js | 96 ++ .../test_provider_unsafe_access_shutdown.js | 65 + .../test_provider_unsafe_access_startup.js | 59 + .../extensions/test/xpcshell/test_proxies.js | 235 ++++ .../test/xpcshell/test_recommendations.js | 712 ++++++++++ .../test/xpcshell/test_registerchrome.js | 88 ++ .../extensions/test/xpcshell/test_registry.js | 160 +++ .../test/xpcshell/test_reinstall_disabled_addon.js | 213 +++ .../extensions/test/xpcshell/test_reload.js | 188 +++ .../extensions/test/xpcshell/test_safemode.js | 90 ++ .../extensions/test/xpcshell/test_schema_change.js | 157 +++ .../mozapps/extensions/test/xpcshell/test_seen.js | 277 ++++ .../extensions/test/xpcshell/test_shutdown.js | 131 ++ .../test/xpcshell/test_shutdown_barriers.js | 218 +++ .../test/xpcshell/test_shutdown_early.js | 62 + .../test/xpcshell/test_sideload_scopes.js | 188 +++ .../extensions/test/xpcshell/test_sideloads.js | 117 ++ .../test/xpcshell/test_sideloads_after_rebuild.js | 149 +++ .../extensions/test/xpcshell/test_signed_inject.js | 429 ++++++ .../test/xpcshell/test_signed_install.js | 337 +++++ .../test/xpcshell/test_signed_langpack.js | 67 + .../extensions/test/xpcshell/test_signed_long.js | 23 + .../test/xpcshell/test_signed_updatepref.js | 130 ++ .../extensions/test/xpcshell/test_signed_verify.js | 109 ++ .../test/xpcshell/test_sitePermsAddonProvider.js | 967 ++++++++++++++ .../extensions/test/xpcshell/test_startup.js | 648 +++++++++ .../test/xpcshell/test_startup_enable.js | 47 + .../test/xpcshell/test_startup_isPrivileged.js | 58 + .../extensions/test/xpcshell/test_startup_scan.js | 125 ++ .../test/xpcshell/test_strictcompatibility.js | 156 +++ .../extensions/test/xpcshell/test_syncGUID.js | 116 ++ .../test/xpcshell/test_system_allowed.js | 54 + .../test/xpcshell/test_system_delay_update.js | 485 +++++++ .../test/xpcshell/test_system_profile_location.js | 204 +++ .../test/xpcshell/test_system_repository.js | 68 + .../extensions/test/xpcshell/test_system_reset.js | 533 ++++++++ .../test/xpcshell/test_system_update_blank.js | 117 ++ .../xpcshell/test_system_update_checkSizeHash.js | 181 +++ .../test/xpcshell/test_system_update_custom.js | 492 +++++++ .../test/xpcshell/test_system_update_empty.js | 141 ++ .../test_system_update_enterprisepolicy.js | 77 ++ .../test/xpcshell/test_system_update_fail.js | 185 +++ .../test_system_update_installTelemetryInfo.js | 94 ++ .../test/xpcshell/test_system_update_newset.js | 165 +++ .../xpcshell/test_system_update_overlapping.js | 180 +++ .../xpcshell/test_system_update_uninstall_check.js | 56 + .../test/xpcshell/test_system_update_upgrades.js | 165 +++ .../test/xpcshell/test_system_upgrades.js | 417 ++++++ .../test/xpcshell/test_systemaddomstartupprefs.js | 55 + .../extensions/test/xpcshell/test_temporary.js | 765 +++++++++++ .../test/xpcshell/test_trash_directory.js | 47 + .../mozapps/extensions/test/xpcshell/test_types.js | 117 ++ .../extensions/test/xpcshell/test_undouninstall.js | 584 ++++++++ .../extensions/test/xpcshell/test_update.js | 834 ++++++++++++ .../extensions/test/xpcshell/test_updateCancel.js | 139 ++ .../test/xpcshell/test_update_addontype.js | 75 ++ .../test/xpcshell/test_update_compatmode.js | 112 ++ .../test/xpcshell/test_update_ignorecompat.js | 116 ++ .../test/xpcshell/test_update_isPrivileged.js | 181 +++ .../xpcshell/test_update_noSystemAddonUpdate.js | 42 + .../test/xpcshell/test_update_strictcompat.js | 216 +++ .../extensions/test/xpcshell/test_update_theme.js | 121 ++ .../test/xpcshell/test_update_webextensions.js | 209 +++ .../extensions/test/xpcshell/test_updatecheck.js | 167 +++ .../test/xpcshell/test_updatecheck_errors.js | 52 + .../test/xpcshell/test_updatecheck_json.js | 379 ++++++ .../extensions/test/xpcshell/test_updateid.js | 82 ++ .../extensions/test/xpcshell/test_updateversion.js | 101 ++ .../extensions/test/xpcshell/test_upgrade.js | 199 +++ .../test/xpcshell/test_upgrade_incompatible.js | 73 + .../extensions/test/xpcshell/test_webextension.js | 676 ++++++++++ .../test/xpcshell/test_webextension_events.js | 94 ++ .../test/xpcshell/test_webextension_icons.js | 212 +++ .../test/xpcshell/test_webextension_install.js | 694 ++++++++++ .../test_webextension_install_syntax_error.js | 42 + .../test/xpcshell/test_webextension_langpack.js | 669 ++++++++++ .../test/xpcshell/test_webextension_paths.js | 47 + .../test/xpcshell/test_webextension_theme.js | 365 +++++ .../extensions/test/xpcshell/xpcshell-unpack.ini | 13 + .../mozapps/extensions/test/xpcshell/xpcshell.ini | 226 ++++ 262 files changed, 43376 insertions(+) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js 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/mlbf-blocked1-unblocked2.bin 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/content_signing_aus_ee.pem create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_aus_ee.pem.certspec create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem.certspec 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/langpack_signed.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/langpack_unsigned.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/privileged.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed1.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed2.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned.xpi create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_empty.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_fail.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.json 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_bug393285.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-extensions.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-plugins.json 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-extensions.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-plugins.json 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_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_bug655254.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_corrupt.json 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_legacy.json 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_legacy.json 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_legacy.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_staged.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.json create mode 100755 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_OSVersion.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_install_addons.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_install_compat.json 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_softblocked1.xml create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_trash_directory.worker.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update_addons.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update_compat.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json 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_amremotesettings.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_cert_handling.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_compat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_sideload.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_unpack.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.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_cache_locale.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_langpacks.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_paging.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_QuarantinedDomains_AMRemoteSettings.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_addonStartup.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_addon_manager_telemetry_events.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_amo_stats_telemetry.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_aom_startup.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_bug587088.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_builtin_location.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_childprocess.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_colorways_builtin_theme_upgrades.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_cookies.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_crash_annotation_quoting.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_db_path.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_webextension.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_distribution.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_distribution_langpack.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_embedderDisabled.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_error.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_general.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_getInstallSourceFromHost.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_harness.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_hidden.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_installOrigins.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_cancel.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_file_change.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_installtrigger_deprecation.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_installtrigger_schemes.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_loadManifest_isPrivileged.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_locale.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_moved_extension_metadata.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_permissions.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.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_recommendations.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_registerchrome.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_registry.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_reinstall_disabled_addon.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_shutdown.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_shutdown_barriers.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_shutdown_early.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_sideload_scopes.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_sideloads.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_sideloads_after_rebuild.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_langpack.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_long.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_sitePermsAddonProvider.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_startup.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_startup_enable.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_startup_isPrivileged.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_startup_scan.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_allowed.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_profile_location.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_repository.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_blank.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_checkSizeHash.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_custom.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_empty.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_enterprisepolicy.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_fail.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_installTelemetryInfo.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_newset.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_overlapping.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_uninstall_check.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update_upgrades.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_upgrades.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_systemaddomstartupprefs.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_temporary.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_trash_directory.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_types.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.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_addontype.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_isPrivileged.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_noSystemAddonUpdate.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_theme.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_updatecheck_errors.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updatecheck_json.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updateid.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updateversion.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_upgrade_incompatible.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_events.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_install_syntax_error.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_paths.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini create mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini (limited to 'toolkit/mozapps/extensions/test/xpcshell') diff --git a/toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js b/toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js new file mode 100644 index 0000000000..8e3971b385 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js @@ -0,0 +1,24 @@ +"use strict"; + +module.exports = { + rules: { + "no-unused-vars": [ + "error", + { args: "none", varsIgnorePattern: "^end_test$" }, + ], + }, + overrides: [ + { + files: "head*.js", + rules: { + "no-unused-vars": [ + "error", + { + args: "none", + vars: "local", + }, + ], + }, + }, + ], +}; diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml new file mode 100644 index 0000000000..1f673ef2fb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_empty.xml b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_empty.xml new file mode 100644 index 0000000000..88d22f281f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_empty.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_start.xml b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_start.xml new file mode 100644 index 0000000000..daba6f4c1c --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_start.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_warn.xml b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_warn.xml new file mode 100644 index 0000000000..232fd0d079 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_warn.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/corrupt.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/corrupt.xpi new file mode 100644 index 0000000000..35d7bd5e5d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/corrupt.xpi @@ -0,0 +1 @@ +This is a corrupt zip file diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/corruptfile.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/corruptfile.xpi new file mode 100644 index 0000000000..0c30989aa5 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/corruptfile.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/empty.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/empty.xpi new file mode 100644 index 0000000000..74ed2b8174 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/empty.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/mlbf-blocked1-unblocked2.bin b/toolkit/mozapps/extensions/test/xpcshell/data/mlbf-blocked1-unblocked2.bin new file mode 100644 index 0000000000..fe8e08fa68 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/mlbf-blocked1-unblocked2.bin differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/pluginInfoURL_block.xml b/toolkit/mozapps/extensions/test/xpcshell/data/pluginInfoURL_block.xml new file mode 100644 index 0000000000..75e252a46b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/pluginInfoURL_block.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + http://test.url.com/ + + + + + + + + + + http://alt.test.url.com/ + + + + + + + + + + + + http://test.url2.com/ + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.txt b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.txt new file mode 100644 index 0000000000..f17f98b15b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.txt @@ -0,0 +1 @@ +Not an xml file! \ No newline at end of file diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.xml b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.xml new file mode 100644 index 0000000000..0e3d415c44 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad2.xml b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad2.xml new file mode 100644 index 0000000000..55ad1c7d55 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad2.xml @@ -0,0 +1,3 @@ + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_aus_ee.pem b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_aus_ee.pem new file mode 100644 index 0000000000..e7933cc864 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_aus_ee.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICRzCCAS+gAwIBAgIUbctVfUWXUmfxzCRUBeXixFrQuOEwDQYJKoZIhvcNAQEL +BQAwETEPMA0GA1UEAwwGaW50LUNBMCIYDzIwMjIxMTI3MDAwMDAwWhgPMjAyNTAy +MDQwMDAwMDBaMA0xCzAJBgNVBAMMAmVlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE +oWhyQzYrXHsYifN5FUYVocc/tI3uhj4CKRXbYI4lLeS3Ey2ozpjoMVNOapwMCwnI +1jmt6DIG5bqBNHOhH6Mw4F2oyW5Dg/4nhz2pcQO+KIjP8ALwWvcaH93Mg3SqbqnO +o0UwQzATBgNVHSUEDDAKBggrBgEFBQcDAzAsBgNVHREEJTAjgiFhdXMuY29udGVu +dC1zaWduYXR1cmUubW96aWxsYS5vcmcwDQYJKoZIhvcNAQELBQADggEBAF5IT9HZ +1ej+FAXbs2e/LOojJAulc2sxbeaa5V3rJWIiSq8iMj/ZV8dRaa96x3M6azdPiJjD +/VT4mNF9/KBC8YoEwfJe4A9MR8SmEIe/2EMIzmZVdTv1LYsKqRuuwvbGFssBj7lW +U9+V5KzjxtKU/RQfak5Iz+vnl6s4LIt92SLdOooPqDGj2K3FI9dg2Fqwm6vF+6zi +8yZ7/zg4PQcY6t2C6l0e9iAFM+wzhtTPq1AvFdq5hdOil6AS8Ivb0elMwBzsLjr5 +COLcKmCeRQ/8JFhJ48C+/MQkp3gbgXvVR3fcufufSC2YaLmMb7MIhaJwNCT0nO87 +ItpI1owSYrJOnQ0= +-----END CERTIFICATE----- diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_aus_ee.pem.certspec b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_aus_ee.pem.certspec new file mode 100644 index 0000000000..ee9fea9110 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_aus_ee.pem.certspec @@ -0,0 +1,5 @@ +issuer:int-CA +subject:ee +subjectKey:secp384r1 +extension:extKeyUsage:codeSigning +extension:subjectAlternativeName:aus.content-signature.mozilla.org diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem new file mode 100644 index 0000000000..6c80b1be43 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC8TCCAdmgAwIBAgIUGU8IXEaU5Al531xp9aITCfLjy/cwDQYJKoZIhvcNAQEL +BQAwKTEnMCUGA1UEAwweeHBjc2hlbGwgc2lnbmVkIGFwcHMgdGVzdCByb290MCIY +DzIwMjIxMTI3MDAwMDAwWhgPMjAyNTAyMDQwMDAwMDBaMBExDzANBgNVBAMMBmlu +dC1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1u +togGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6 +pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqL +KkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3Zlqq +fgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3sv +Im9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6za +GAo17Y0CAwEAAaMlMCMwDAYDVR0TBAUwAwEB/zATBgNVHSUEDDAKBggrBgEFBQcD +AzANBgkqhkiG9w0BAQsFAAOCAQEAQw8azGUnMeiHd6BYf8LZDK2dqsbVpWuDT/td +LNQcYStX4jgPSfSxm9Mg6osXBnEKF83qXoNeP6Zt84WSJDotEf0WlC5JfNZFCMry +vfd7odumxp/00LYaMbVK8Wz2LXXXwjsYF8xoZz6zq1DYviXIMluhcvCMepnCUnbP +hY12tcznmHiHCOoEB1qurCfW8MkIz/GkLa409i7wFE9rsAeuAKgtdTStY5g8qp5j +2KpmTzgfCeDgKwOSEUyW4YZXrvHYpPSnLiFsWvdxG3/D9aZExw1fipvzhpvqZYv9 +u2e7Qpt98Cd+Kitom/uDNmX9hv6E3eBThQI8QpTf43z6w/KD4A== +-----END CERTIFICATE----- diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem.certspec b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem.certspec new file mode 100644 index 0000000000..fc9dfd47ae --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/content_signing_int.pem.certspec @@ -0,0 +1,4 @@ +issuer:xpcshell signed apps test root +subject:int-CA +extension:basicConstraints:cA, +extension:extKeyUsage:codeSigning diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/empty.xml b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/empty.xml new file mode 100644 index 0000000000..42cb20bd01 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/empty.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml new file mode 100644 index 0000000000..e1da86fa54 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/missing.xml b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/missing.xml new file mode 100644 index 0000000000..8c9501478e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/missing.xml @@ -0,0 +1,3 @@ + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/unsigned.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/unsigned.xpi new file mode 100644 index 0000000000..51b00475a9 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/productaddons/unsigned.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/langpack_signed.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/langpack_signed.xpi new file mode 100644 index 0000000000..f60d00348e Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/langpack_signed.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/langpack_unsigned.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/langpack_unsigned.xpi new file mode 100644 index 0000000000..89de7f4409 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/langpack_unsigned.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long.xpi new file mode 100644 index 0000000000..f95f3df91e Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/privileged.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/privileged.xpi new file mode 100644 index 0000000000..c22acaacd2 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/privileged.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed1.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed1.xpi new file mode 100644 index 0000000000..e2ba7d6fd8 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed1.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed2.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed2.xpi new file mode 100644 index 0000000000..ccb20796f2 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed2.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned.xpi new file mode 100644 index 0000000000..9e10be5db3 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.json new file mode 100644 index 0000000000..a9fdcf1782 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.json @@ -0,0 +1,134 @@ +{ + "page_size": 25, + "page_count": 1, + "count": 4, + "next": null, + "previous": null, + "results": [ + { + "name": "Repo Add-on 1", + "type": "extension", + "guid": "test_AddonRepository_1@tests.mozilla.org", + "current_version": { + "version": "2.1", + "files": [ + { + "platform": "all", + "size": 9, + "url": "http://example.com/repo/1/install.xpi" + } + ] + }, + "authors": [ + { + "name": "Repo Add-on 1 - Creator", + "url": "http://example.com/repo/1/creator.html" + }, + { + "name": "Repo Add-on 1 - First Developer", + "url": "http://example.com/repo/1/firstDeveloper.html" + }, + { + "name": "Repo Add-on 1 - Second Developer", + "url": "http://example.com/repo/1/secondDeveloper.html" + } + ], + "summary": "Repo Add-on 1 - Description
Second line", + "description": "

Repo Add-on 1 - Full Description & some extra

", + "icons": { + "32": "http://example.com/repo/1/icon.png" + }, + "ratings": { + "count": 1234, + "text_count": 1111, + "average": 1 + }, + "homepage": "http://example.com/repo/1/homepage.html", + "support_url": "http://example.com/repo/1/support.html", + "contributions_url": "http://example.com/repo/1/meetDevelopers.html", + "ratings_url": "http://example.com/repo/1/review.html", + "weekly_downloads": 3331, + "last_updated": "1970-01-01T00:00:09Z" + }, + { + "name": "Repo Add-on 2", + "type": "theme", + "guid": "test_AddonRepository_2@tests.mozilla.org", + "current_version": { + "version": "2.2", + "files": [ + { + "platform": "all", + "size": 9, + "url": "http://example.com/repo/2/install.xpi" + } + ] + }, + "authors": [ + { + "name": "Repo Add-on 2 - Creator", + "url": "http://example.com/repo/2/creator.html" + }, + { + "name": "Repo Add-on 2 - First Developer", + "url": "http://example.com/repo/2/firstDeveloper.html" + }, + { + "name": "Repo Add-on 2 - Second Developer", + "url": "http://example.com/repo/2/secondDeveloper.html" + } + ], + "summary": "Repo Add-on 2 - Description", + "description": "Repo Add-on 2 - Full Description", + "icons": { + "32": "http://example.com/repo/2/icon.png" + }, + "previews": [ + { + "image_url": "http://example.com/repo/2/firstFull.png", + "thumbnail_url": "http://example.com/repo/2/firstThumbnail.png", + "caption": "Repo Add-on 2 - First Caption" + }, + { + "image_url": "http://example.com/repo/2/secondFull.png", + "thumbnail_url": "http://example.com/repo/2/secondThumbnail.png", + "caption": "Repo Add-on 2 - Second Caption" + } + ], + "ratings": { + "count": 2223, + "text_count": 1112, + "average": 2 + }, + "homepage": "http://example.com/repo/2/homepage.html", + "support_url": "http://example.com/repo/2/support.html", + "contributions_url": "http://example.com/repo/2/meetDevelopers.html", + "ratings_url": "http://example.com/repo/2/review.html", + "weekly_downloads": 3332, + "last_updated": "1970-01-01T00:00:09Z" + }, + { + "name": "Repo Add-on 3", + "type": "theme", + "guid": "test_AddonRepository_3@tests.mozilla.org", + "current_version": { + "version": "2.3" + }, + "icons": { + "32": "http://example.com/repo/3/icon.png" + }, + "previews": [ + { + "image_url": "http://example.com/repo/3/firstFull.png", + "thumbnail_url": "http://example.com/repo/3/firstThumbnail.png", + "caption": "Repo Add-on 3 - First Caption" + }, + { + "image_url": "http://example.com/repo/3/secondFull.png", + "thumbnail_url": "http://example.com/repo/3/secondThumbnail.png", + "caption": "Repo Add-on 3 - Second Caption" + } + ] + } + ] +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_empty.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_empty.json new file mode 100644 index 0000000000..c6c09cdf92 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_empty.json @@ -0,0 +1,7 @@ +{ + "page_size": 25, + "count": 0, + "next": null, + "previous": null, + "results": [] +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_fail.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_fail.json new file mode 100644 index 0000000000..d29d525a81 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_fail.json @@ -0,0 +1 @@ +this should yield a json parse error diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.json new file mode 100644 index 0000000000..6f83c5bdd2 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.json @@ -0,0 +1,116 @@ +{ + "page_size": 25, + "page_count": 1, + "count": 4, + "next": null, + "previous": null, + "results": [ + { + "name": "PASS", + "type": "extension", + "guid": "test1@tests.mozilla.org", + "current_version": { + "version": "1.1", + "files": [ + { + "platform": "all", + "url": "http://example.com/addons/test_AddonRepository_2.xpi", + "size": 5555 + } + ] + }, + "authors": [ + { + "name": "Test Creator 1", + "url": "http://example.com/creator1.html" + }, + { + "name": "Test Developer 1", + "url": "http://example.com/developer1.html" + } + ], + "summary": "Test Summary 1", + "description": "Test Description 1", + "icons": { + "32": "http://example.com/icon1.png" + }, + "previews": [ + { + "caption": "Caption 1 - 1", + "image_size": [400, 300], + "image_url": "http://example.com/full1-1.png", + "thumbnail_size": [200, 150], + "thumbnail_url": "http://example.com/thumbnail1-1.png" + }, + { + "caption": "Caption 2 - 1", + "image_url": "http://example.com/full2-1.png", + "thumbnail_url": "http://example.com/thumbnail2-1.png" + } + ], + "ratings": { + "count": 1234, + "text_count": 1111, + "average": 4 + }, + "ratings_url": "http://example.com/review1.html", + "support_url": "http://example.com/support1.html", + "contributions_url": "http://example.com/contribution1.html", + "weekly_downloads": 3333, + "last_updated": "2010-02-01T14:04:05Z" + }, + { + "name": "PASS", + "type": "extension", + "guid": "test2@tests.mozilla.org", + "current_version": { + "version": "2.0", + "files": [ + { + "platform": "XPCShell", + "url": "http://example.com/addons/bleah.xpi", + "size": 1000 + } + ] + } + }, + { + "name": "FAIL", + "type": "extension", + "guid": "notRequested@tests.mozilla.org", + "current_version": { + "version": "1.3", + "files": [ + { + "platform": "all", + "url": "http://example.com/test3.xpi" + } + ] + }, + "authors": [ + { + "name": "Test Creator 3" + } + ], + "summary": "Add-on with a guid that wasn't requested should be ignored." + }, + { + "name": "PASS", + "type": "theme", + "guid": "test_AddonRepository_1@tests.mozilla.org", + "current_version": { + "version": "1.4", + "files": [ + { + "platform": "UNKNOWN1", + "url": "http://example.com/test4.xpi" + }, + { + "platform": "UNKNOWN2", + "url": "http://example.com/test4.xpi" + } + ] + } + } + ] +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.json new file mode 100644 index 0000000000..b83e0b04ba --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.json @@ -0,0 +1,46 @@ +{ + "addons": { + "addon2@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + }, + "version": "2", + "update_link": "http://example.com/broken.xpi" + } + ] + }, + "addon3@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + }, + "version": "2", + "update_link": "http://example.com/broken.xpi" + } + ] + }, + "addon1@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + }, + "version": "2", + "update_link": "http://example.com/broken.xpi" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml new file mode 100644 index 0000000000..b092418bbb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_prefs_1.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_prefs_1.xml new file mode 100644 index 0000000000..41df457b05 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_prefs_1.xml @@ -0,0 +1,28 @@ + + + + + + + test.blocklist.pref1 + test.blocklist.pref2 + + + + + + + + + + test.blocklist.pref3 + test.blocklist.pref4 + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml new file mode 100644 index 0000000000..1767b4332f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-extensions.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-extensions.json new file mode 100644 index 0000000000..2c1fff10c5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-extensions.json @@ -0,0 +1,332 @@ +[ + { + "_comment": "Always blocked", + "guid": "test_bug449027_2@tests.mozilla.org", + "versionRange": [] + }, + { + "_comment": "Always blocked", + "guid": "test_bug449027_3@tests.mozilla.org", + "versionRange": [{}] + }, + { + "_comment": "Not blocked since neither version range matches", + "guid": "test_bug449027_4@tests.mozilla.org", + "versionRange": [ + { + "minVersion": "6" + }, + { + "maxVersion": "4" + } + ] + }, + { + "_comment": "Invalid version range, should not block", + "guid": "test_bug449027_5@tests.mozilla.org", + "versionRange": [ + { + "maxVersion": "4", + "minVersion": "6" + } + ] + }, + { + "_comment": "Should block all of these", + "guid": "test_bug449027_6@tests.mozilla.org", + "versionRange": [ + { + "maxVersion": "8", + "minVersion": "7" + }, + { + "maxVersion": "6", + "minVersion": "5" + }, + { + "maxVersion": "4" + } + ] + }, + { + "guid": "test_bug449027_7@tests.mozilla.org", + "versionRange": [ + { + "maxVersion": "4" + }, + { + "maxVersion": "5", + "minVersion": "4" + }, + { + "maxVersion": "7", + "minVersion": "6" + } + ] + }, + { + "guid": "test_bug449027_8@tests.mozilla.org", + "versionRange": [ + { + "maxVersion": "2", + "minVersion": "2" + }, + { + "maxVersion": "6", + "minVersion": "4" + }, + { + "maxVersion": "8", + "minVersion": "7" + } + ] + }, + { + "guid": "test_bug449027_9@tests.mozilla.org", + "versionRange": [ + { + "minVersion": "4" + } + ] + }, + { + "guid": "test_bug449027_10@tests.mozilla.org", + "versionRange": [ + { + "minVersion": "5" + } + ] + }, + { + "guid": "test_bug449027_11@tests.mozilla.org", + "versionRange": [ + { + "maxVersion": "6" + } + ] + }, + { + "guid": "test_bug449027_12@tests.mozilla.org", + "versionRange": [ + { + "maxVersion": "5" + } + ] + }, + { + "_comment": "This should block all versions for any application", + "guid": "test_bug449027_13@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [{}] + } + ] + }, + { + "_comment": "Shouldn't block", + "guid": "test_bug449027_14@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "_comment": "Should block for any version of the app", + "guid": "test_bug449027_15@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Should still block", + "guid": "test_bug449027_16@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Not blocked since neither version range matches", + "guid": "test_bug449027_17@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "minVersion": "4" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "2" + } + ] + } + ] + }, + { + "_comment": "Invalid version range, should not block", + "guid": "test_bug449027_18@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4", + "minVersion": "6" + } + ] + } + ] + }, + { + "_comment": "Should block all of these", + "guid": "test_bug449027_19@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "6", + "minVersion": "5" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4", + "minVersion": "3" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "2" + } + ] + } + ] + }, + { + "guid": "test_bug449027_20@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "2" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "3", + "minVersion": "2" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "5", + "minVersion": "4" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "guid": "test_bug449027_21@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "1", + "minVersion": "1" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4", + "minVersion": "2" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "6", + "minVersion": "5" + } + ] + } + ] + }, + { + "guid": "test_bug449027_22@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "minVersion": "3" + } + ] + } + ] + }, + { + "guid": "test_bug449027_23@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "minVersion": "2" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "guid": "test_bug449027_24@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "3" + } + ] + } + ] + }, + { + "guid": "test_bug449027_25@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4" + } + ] + } + ] + } +] diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-plugins.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-plugins.json new file mode 100644 index 0000000000..c88088c9b3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-plugins.json @@ -0,0 +1,332 @@ +[ + { + "_comment": "Always blocked", + "matchName": "^test_bug449027_2$", + "versionRange": [] + }, + { + "_comment": "Always blocked", + "matchName": "^test_bug449027_3$", + "versionRange": [{}] + }, + { + "_comment": "Not blocked since neither version range matches", + "matchName": "^test_bug449027_4$", + "versionRange": [ + { + "minVersion": "6" + }, + { + "maxVersion": "4" + } + ] + }, + { + "_comment": "Invalid version range, should not block", + "matchName": "^test_bug449027_5$", + "versionRange": [ + { + "maxVersion": "4", + "minVersion": "6" + } + ] + }, + { + "_comment": "Should block all of these", + "matchName": "^test_bug449027_6$", + "versionRange": [ + { + "maxVersion": "8", + "minVersion": "7" + }, + { + "maxVersion": "6", + "minVersion": "5" + }, + { + "maxVersion": "4" + } + ] + }, + { + "matchName": "^test_bug449027_7$", + "versionRange": [ + { + "maxVersion": "4" + }, + { + "maxVersion": "5", + "minVersion": "4" + }, + { + "maxVersion": "7", + "minVersion": "6" + } + ] + }, + { + "matchName": "^test_bug449027_8$", + "versionRange": [ + { + "maxVersion": "2", + "minVersion": "2" + }, + { + "maxVersion": "6", + "minVersion": "4" + }, + { + "maxVersion": "8", + "minVersion": "7" + } + ] + }, + { + "matchName": "^test_bug449027_9$", + "versionRange": [ + { + "minVersion": "4" + } + ] + }, + { + "matchName": "^test_bug449027_10$", + "versionRange": [ + { + "minVersion": "5" + } + ] + }, + { + "matchName": "^test_bug449027_11$", + "versionRange": [ + { + "maxVersion": "6" + } + ] + }, + { + "matchName": "^test_bug449027_12$", + "versionRange": [ + { + "maxVersion": "5" + } + ] + }, + { + "_comment": "This should block all versions for any application", + "matchName": "^test_bug449027_13$", + "versionRange": [ + { + "targetApplication": [{}] + } + ] + }, + { + "_comment": "Shouldn't block", + "matchName": "^test_bug449027_14$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "_comment": "Should block for any version of the app", + "matchName": "^test_bug449027_15$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Should still block", + "matchName": "^test_bug449027_16$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Not blocked since neither version range matches", + "matchName": "^test_bug449027_17$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "minVersion": "4" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "2" + } + ] + } + ] + }, + { + "_comment": "Invalid version range, should not block", + "matchName": "^test_bug449027_18$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4", + "minVersion": "6" + } + ] + } + ] + }, + { + "_comment": "Should block all of these", + "matchName": "^test_bug449027_19$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "6", + "minVersion": "5" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4", + "minVersion": "3" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "2" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_20$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "2" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "3", + "minVersion": "2" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "5", + "minVersion": "4" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_21$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "1", + "minVersion": "1" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4", + "minVersion": "2" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "6", + "minVersion": "5" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_22$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "xpcshell@tests.mozilla.org", + "minVersion": "3" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_23$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "minVersion": "2" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_24$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "3" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_25$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "xpcshell@tests.mozilla.org", + "maxVersion": "4" + } + ] + } + ] + } +] diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app.xml new file mode 100644 index 0000000000..f12ca1fa6d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app.xml @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-extensions.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-extensions.json new file mode 100644 index 0000000000..107079fd41 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-extensions.json @@ -0,0 +1,189 @@ +[ + { + "_general_comment": "All extensions are version 5 and tests run against toolkitVersion 8", + "_general_comment2": "Test 1-14 not listed, should never get blocked", + + "_comment": "Should block for any version of the app", + "guid": "test_bug449027_15@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [{ "guid": "toolkit@mozilla.org" }] + } + ] + }, + { + "_comment": "Should still block", + "guid": "test_bug449027_16@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [{ "guid": "toolkit@mozilla.org" }] + } + ] + }, + { + "_comment": "Not blocked since neither version range matches", + "guid": "test_bug449027_17@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "minVersion": "9", + "guid": "toolkit@mozilla.org" + }, + { + "maxVersion": "7", + "guid": "toolkit@mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Invalid version range, should not block", + "guid": "test_bug449027_18@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "minVersion": "11", + "maxVersion": "9", + "guid": "toolkit@mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Should block all of the following", + "guid": "test_bug449027_19@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "10", + "maxVersion": "11" + }, + { + "minVersion": "8", + "maxVersion": "9" + }, + { + "maxVersion": "7" + } + ] + } + ] + }, + { + "guid": "test_bug449027_20@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "maxVersion": "7" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "7", + "maxVersion": "8" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "9", + "maxVersion": "10" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "guid": "test_bug449027_21@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "minVersion": "6", + "maxVersion": "6" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "7", + "maxVersion": "9" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "10", + "maxVersion": "11" + } + ] + } + ] + }, + { + "guid": "test_bug449027_22@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "8" + } + ] + } + ] + }, + { + "guid": "test_bug449027_23@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "minVersion": "7" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "guid": "test_bug449027_24@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "maxVersion": "8", + "guid": "toolkit@mozilla.org" + } + ] + } + ] + }, + { + "guid": "test_bug449027_25@tests.mozilla.org", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "maxVersion": "9" + } + ] + } + ] + } +] diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-plugins.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-plugins.json new file mode 100644 index 0000000000..c3565d2073 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-plugins.json @@ -0,0 +1,189 @@ +[ + { + "_general_comment": "All plugins are version 5 and tests run against appVersion 3", + "_general_comment2": "Test 1-14 not listed, should never get blocked", + + "_comment": "Should block for any version of the app", + "matchName": "^test_bug449027_15$", + "versionRange": [ + { + "targetApplication": [{ "guid": "toolkit@mozilla.org" }] + } + ] + }, + { + "_comment": "Should still block", + "matchName": "^test_bug449027_16$", + "versionRange": [ + { + "targetApplication": [{ "guid": "toolkit@mozilla.org" }] + } + ] + }, + { + "_comment": "Not blocked since neither version range matches", + "matchName": "^test_bug449027_17$", + "versionRange": [ + { + "targetApplication": [ + { + "minVersion": "9", + "guid": "toolkit@mozilla.org" + }, + { + "maxVersion": "7", + "guid": "toolkit@mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Invalid version range, should not block", + "matchName": "^test_bug449027_18$", + "versionRange": [ + { + "targetApplication": [ + { + "minVersion": "11", + "maxVersion": "9", + "guid": "toolkit@mozilla.org" + } + ] + } + ] + }, + { + "_comment": "Should block all of the following", + "matchName": "^test_bug449027_19$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "10", + "maxVersion": "11" + }, + { + "minVersion": "8", + "maxVersion": "9" + }, + { + "maxVersion": "7" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_20$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "maxVersion": "7" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "7", + "maxVersion": "8" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "9", + "maxVersion": "10" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_21$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "minVersion": "6", + "maxVersion": "6" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "7", + "maxVersion": "9" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "10", + "maxVersion": "11" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_22$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "foo@bar.com" + }, + { + "guid": "toolkit@mozilla.org", + "minVersion": "8" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_23$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "minVersion": "7" + }, + { + "guid": "foo@bar.com" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_24$", + "versionRange": [ + { + "targetApplication": [ + { + "maxVersion": "8", + "guid": "toolkit@mozilla.org" + } + ] + } + ] + }, + { + "matchName": "^test_bug449027_25$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "toolkit@mozilla.org", + "maxVersion": "9" + } + ] + } + ] + } +] diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit.xml new file mode 100644 index 0000000000..ad8ec5ed9d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit.xml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug468528.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug468528.xml new file mode 100644 index 0000000000..85f0da57ce --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug468528.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_1.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_1.xml new file mode 100644 index 0000000000..c4cc2fe37a --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_1.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_2.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_2.xml new file mode 100644 index 0000000000..cc0a0c69df --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_2.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_empty.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_empty.xml new file mode 100644 index 0000000000..0261794f8a --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_empty.xml @@ -0,0 +1,4 @@ + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_1.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_1.xml new file mode 100644 index 0000000000..d651f87996 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_2.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_2.xml new file mode 100644 index 0000000000..208444681e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.json new file mode 100644 index 0000000000..3b1dd81dab --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.json @@ -0,0 +1,17 @@ +{ + "addons": { + "addon1@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "2" + } + }, + "version": "1" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_corrupt.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_corrupt.json new file mode 100644 index 0000000000..7cb48d4798 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_corrupt.json @@ -0,0 +1,30 @@ +{ + "addons": { + "addon3@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "2" + } + }, + "version": "1.0" + } + ] + }, + "addon4@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "2" + } + }, + "version": "1.0" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.json new file mode 100644 index 0000000000..b79dc236c3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.json @@ -0,0 +1,12 @@ +{ + "addons": { + "test_delay_update_complete_webext@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_complete_webextension_v2.xpi" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete_legacy.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete_legacy.json new file mode 100644 index 0000000000..125d1b1a91 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete_legacy.json @@ -0,0 +1,18 @@ +{ + "addons": { + "test_delay_update_complete@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + }, + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_complete_v2.xpi" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.json new file mode 100644 index 0000000000..c2ea01e8c5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.json @@ -0,0 +1,12 @@ +{ + "addons": { + "test_delay_update_defer_webext@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_defer_webextension_v2.xpi" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer_legacy.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer_legacy.json new file mode 100644 index 0000000000..d434fe2e17 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer_legacy.json @@ -0,0 +1,18 @@ +{ + "addons": { + "test_delay_update_defer@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + }, + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_defer_v2.xpi" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.json new file mode 100644 index 0000000000..5d5dc262cb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.json @@ -0,0 +1,12 @@ +{ + "addons": { + "test_delay_update_ignore_webext@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_ignore_webextension_v2.xpi" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore_legacy.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore_legacy.json new file mode 100644 index 0000000000..bc46fab8fd --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore_legacy.json @@ -0,0 +1,18 @@ +{ + "addons": { + "test_delay_update_ignore@tests.mozilla.org": { + "updates": [ + { + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + }, + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_ignore_v2.xpi" + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_staged.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_staged.json new file mode 100644 index 0000000000..e0611edb35 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_staged.json @@ -0,0 +1,32 @@ +{ + "addons": { + "test_delay_update_staged_webext@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_staged_webextension_v2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "43" + } + } + } + ] + }, + "test_delay_update_staged_webext_no_update_url@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/addons/test_delay_update_staged_webextension_no_update_url_v2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "43" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.json new file mode 100644 index 0000000000..6f5d61288d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.json @@ -0,0 +1,377 @@ +[ + { + "blockID": "g35", + "os": "WINNT 6.1", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.2202 ", + "driverVersionComparator": " LESS_THAN " + }, + { + "os": "WINNT 6.0", + "vendor": "0xdcba", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_9_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.2202 ", + "driverVersionComparator": " LESS_THAN " + }, + { + "blockID": "g36", + "os": "WINNT 6.1", + "vendor": "0xabab", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.2202 ", + "driverVersionComparator": " GREATER_THAN_OR_EQUAL " + }, + { + "os": "WINNT 6.1", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1111 ", + "driverVersionComparator": " EQUAL " + }, + { + "os": "Darwin 13", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + { + "os": "Linux", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + { + "os": "Android", + "vendor": "abcd", + "devices": ["wxyz", "asdf", "erty"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionComparator": " LESS_THAN_OR_EQUAL " + }, + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop", "vbnm", "hjkl"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionComparator": " EQUAL " + }, + { + "os": "Android", + "vendor": "abab", + "devices": ["ghjk", "cvbn"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 7 ", + "driverVersionComparator": " GREATER_THAN_OR_EQUAL " + }, + { + "os": "WINNT 6.1", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DEVICE " + }, + { + "os": "Darwin 13", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DEVICE " + }, + { + "os": "Linux", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DEVICE " + }, + { + "os": "Android", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DEVICE " + }, + { + "os": "WINNT 6.1", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " WEBRENDER ", + "featureStatus": " BLOCKED_DEVICE " + }, + { + "os": "Darwin 13", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " WEBRENDER ", + "featureStatus": " BLOCKED_DEVICE " + }, + { + "os": "Linux", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " WEBRENDER ", + "featureStatus": " BLOCKED_DEVICE " + }, + { + "os": "Android", + "vendor": "0xabcd", + "devices": ["0x6666"], + "feature": " WEBRENDER ", + "featureStatus": " BLOCKED_DEVICE " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1112 ", + "driverVersionMax": " 8.52.323.1000 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " OPENGL_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.50.322.1000 ", + "driverVersionMax": " 8.52.322.1112 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1000 ", + "driverVersionMax": " 9.52.322.1000 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " HARDWARE_VIDEO_DECODING ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 7.82.322.1000 ", + "driverVersionMax": " 9.25.322.1001 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_H264 ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1112 ", + "driverVersionMax": " 9.52.322.1300 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_DECODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1000 ", + "driverVersionMax": " 8.52.322.1112 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_ENCODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1112 ", + "driverVersionMax": " 8.52.322.1200 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_MSAA ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1000 ", + "driverVersionMax": " 8.52.322.1200 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1000 ", + "driverVersionMax": " 8.52.322.1112 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL2 ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1000 ", + "driverVersionMax": " 8.52.322.1112 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + + { + "os": "All", + "vendor": "0xdcdc", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " CANVAS2D_ACCELERATION ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.1000 ", + "driverVersionMax": " 9.52.322.1000 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " DIRECT3D_11_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 6 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " OPENGL_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 6 ", + "driverVersionMax": " 7 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " DIRECT3D_11_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 7 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " HARDWARE_VIDEO_DECODING ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 7 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " WEBRTC_HW_ACCELERATION_H264 ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 6 ", + "driverVersionMax": " 7 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " WEBRTC_HW_ACCELERATION_DECODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 6 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " WEBRTC_HW_ACCELERATION_ENCODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 6 ", + "driverVersionMax": " 7 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " WEBGL_MSAA ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 7 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " WEBGL_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 6 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " WEBGL2 ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 6 ", + "driverVersionComparator": " BETWEEN_INCLUSIVE_START " + }, + + { + "os": "Android", + "vendor": "dcdc", + "devices": ["uiop"], + "feature": " CANVAS2D_ACCELERATION ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 5 ", + "driverVersionMax": " 7 ", + "driverVersionComparator": " BETWEEN_EXCLUSIVE " + } +] diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.json new file mode 100755 index 0000000000..3f44eb330f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.json @@ -0,0 +1,581 @@ +[ + { + "blockID": "g1", + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g2", + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "22.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_9_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_LAYERS", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1", "maxVersion": "22.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_1_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " OPENGL_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g11", + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "14.0b2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_OPENGL ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL2 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_MSAA ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " STAGEFRIGHT ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_H264 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_ENCODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_DECODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "17.2a2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "13.2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " HARDWARE_VIDEO_DECODING ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "All", + "vendor": "0xabcd", + "versionRange": { "minVersion": "10.5", "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g1", + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g2", + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "22.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_9_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_LAYERS", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1", "maxVersion": "22.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_1_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " OPENGL_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g11", + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "14.0b2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_OPENGL ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL2 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_MSAA ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " STAGEFRIGHT ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_H264 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_ENCODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_DECODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "17.2a2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "13.2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " HARDWARE_VIDEO_DECODING ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Darwin 13", + "vendor": "0xabcd", + "versionRange": { "minVersion": "10.5", "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g1", + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g2", + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "22.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_9_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_LAYERS", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1", "maxVersion": "22.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_1_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " OPENGL_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g11", + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "14.0b2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_OPENGL ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL2 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_MSAA ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " STAGEFRIGHT ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_H264 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_ENCODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_DECODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "17.2a2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "13.2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " HARDWARE_VIDEO_DECODING ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Linux", + "vendor": "0xabcd", + "versionRange": { "minVersion": "10.5", "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g1", + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g2", + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "22.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_9_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_LAYERS", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "16.0a1", "maxVersion": "22.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_10_1_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " OPENGL_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "blockID": "g11", + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "14.0b2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_OPENGL ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL2 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "12.0", "maxVersion": "16.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBGL_MSAA ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " STAGEFRIGHT ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_H264 ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_ENCODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "42.0", "maxVersion": "13.0b2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " WEBRTC_HW_ACCELERATION_DECODE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "17.2a2", "maxVersion": "15.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "15.0", "maxVersion": "13.2" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " HARDWARE_VIDEO_DECODING ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + }, + + { + "os": "Android", + "vendor": "0xabcd", + "versionRange": { "minVersion": "10.5", "maxVersion": "13.0" }, + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT3D_11_ANGLE ", + "featureStatus": " BLOCKED_DRIVER_VERSION " + } +] diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_OSVersion.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_OSVersion.json new file mode 100644 index 0000000000..c80bf3eedd --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_OSVersion.json @@ -0,0 +1,20 @@ +[ + { + "os": "WINNT 6.2", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " DIRECT2D ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.2202 ", + "driverVersionComparator": " LESS_THAN " + }, + { + "os": "Darwin 13", + "vendor": "0xabcd", + "devices": ["0x2783", "0x1234", "0x2782"], + "feature": " OPENGL_LAYERS ", + "featureStatus": " BLOCKED_DRIVER_VERSION ", + "driverVersion": " 8.52.322.2202 ", + "driverVersionComparator": " LESS_THAN " + } +] diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_install_addons.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_install_addons.json new file mode 100644 index 0000000000..d7307831af --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_install_addons.json @@ -0,0 +1,31 @@ +{ + "page_size": 25, + "page_count": 1, + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "name": "Real Test 2", + "type": "extension", + "guid": "addon2@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "size": 2, + "url": "http://example.com/browser/toolkit/mozapps/extensions/test/browser/addons/browser_install1_2.xpi" + } + ] + }, + "authors": [ + { + "name": "Test Creator", + "url": "http://example.com/creator.html" + } + ], + "summary": "Repository summary", + "description": "Repository description" + } + ] +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_install_compat.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_install_compat.json new file mode 100644 index 0000000000..93d0cf3d3d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_install_compat.json @@ -0,0 +1,27 @@ +{ + "page_size": 25, + "page_count": 1, + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "addon_guid": "addon6@tests.mozilla.org", + "name": "Addon Test 6", + "version_ranges": [ + { + "addon_min_version": "1.0", + "addon_max_version": "1.0", + "applications": [ + { + "name": "XPCShell", + "guid": "xpcshell@tests.mozilla.org", + "min_version": "1.0", + "max_version": "1.0" + } + ] + } + ] + } + ] +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_no_update.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_no_update.json new file mode 100644 index 0000000000..2773c7f98f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_no_update.json @@ -0,0 +1,7 @@ +{ + "addons": { + "test_no_update_webext@tests.mozilla.org": { + "updates": [] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/ancient.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/ancient.xml new file mode 100644 index 0000000000..699257f87e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/ancient.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/new.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/new.xml new file mode 100644 index 0000000000..8cbfb5d6a0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/new.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/old.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/old.xml new file mode 100644 index 0000000000..75bd6e934c --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/old.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtp.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtp.xml new file mode 100644 index 0000000000..937d8a5901 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtp.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtpUndo.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtpUndo.xml new file mode 100644 index 0000000000..162876230e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtpUndo.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_softblocked1.xml b/toolkit/mozapps/extensions/test/xpcshell/data/test_softblocked1.xml new file mode 100644 index 0000000000..a1d18470c8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_softblocked1.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_trash_directory.worker.js b/toolkit/mozapps/extensions/test/xpcshell/data/test_trash_directory.worker.js new file mode 100644 index 0000000000..8963bd21e4 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_trash_directory.worker.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/. */ + +/* eslint-env mozilla/chrome-worker */ + +/* import-globals-from /toolkit/components/workerloader/require.js */ +importScripts("resource://gre/modules/workers/require.js"); + +const PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); + +class OpenFileWorker extends PromiseWorker.AbstractWorker { + constructor() { + super(); + + this._file = null; + } + + postMessage(message, ...transfers) { + self.postMessage(message, transfers); + } + + dispatch(method, args) { + return this[method](...args); + } + + open(path) { + this._file = IOUtils.openFileForSyncReading(path); + } + + close() { + if (this._file) { + this._file.close(); + } + } +} + +const worker = new OpenFileWorker(); + +self.addEventListener("message", msg => worker.handleMessage(msg)); +self.addEventListener("unhandledrejection", err => { + throw err.reason; +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json new file mode 100644 index 0000000000..9e3483d520 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json @@ -0,0 +1,137 @@ +{ + "addons": { + "addon1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + }, + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_min_version": "2" + } + } + }, + { + "version": "2.0", + "update_link": "http://example.com/addons/test_update.xpi", + "update_info_url": "http://example.com/updateInfo.xhtml", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon3@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "3", + "advisory_max_version": "3" + } + } + } + ] + }, + + "addon4@tests.mozilla.org": { + "updates": [ + { + "version": "5.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "0" + } + } + } + ] + }, + + "addon7@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon8@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/addons/test_update8.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon12@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/addons/test_update12.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update_addons.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update_addons.json new file mode 100644 index 0000000000..d9777335a6 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update_addons.json @@ -0,0 +1,14 @@ +{ + "page_size": 25, + "page_count": 1, + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "name": "Ttest Addon 9", + "type": "extension", + "guid": "addon9@tests.mozilla.org" + } + ] +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update_compat.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update_compat.json new file mode 100644 index 0000000000..cc2cc15ad5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update_compat.json @@ -0,0 +1,28 @@ +{ + "page_size": 25, + "page_count": 1, + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "addon_guid": "addon9@tests.mozilla.org", + "name": "Test Addon 9", + "version_ranges": [ + { + "addon_min_version": "4", + "addon_max_version": "4", + "applications": [ + { + "name": "XPCShell", + "id": "XPCShell", + "guid": "xpcshell@tests.mozilla.org", + "min_version": "1", + "max_version": "1" + } + ] + } + ] + } + ] +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json new file mode 100644 index 0000000000..f61bfeacd3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json @@ -0,0 +1,269 @@ +{ + "addons": { + "updatecheck1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "https://example.com/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "2.0", + "update_link": "https://example.com/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + }, + { + "version": "3.0", + "update_link": "https://example.com/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "version": "2.0", + "update_link": "https://example.com/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "4.0", + "update_link": "https://example.com/addons/test4.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_8@tests.mozilla.org": { + "_comment_": "The updateLink will be ignored since it is not secure and there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_9@tests.mozilla.org": { + "_comment_": "The updateLink will used since there is an updateHash to verify it.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/broken.xpi", + "update_hash": "sha256:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_10@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://example.com/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_11@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://example.com/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_12@tests.mozilla.org": { + "_comment_": "The updateLink will not be used since the updateHash verifying it is not strong enough.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://example.com/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_13@tests.mozilla.org": { + "_comment_": "An update with a weak hash. The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://example.com/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "_comment_": "There should be no information present for test_bug378216_14", + + "test_bug378216_15@tests.mozilla.org": { + "_comment_": "Invalid update JSON", + + "updates": "foo" + }, + + "ignore-compat@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "version": "1.0", + "update_link": "https://example.com/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "version": "2.0", + "update_link": "https://example.com/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://example.com/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-override@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "_comment_": "Has compatibility override, but it doesn't match this app version", + "version": "1.0", + "update_link": "https://example.com/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "_comment_": "Has compatibility override, so is incompaible", + "version": "2.0", + "update_link": "https://example.com/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://example.com/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-strict-optin@tests.mozilla.org": { + "_comment_": "Opt-in to strict compatibility checking", + + "updates": [ + { + "version": "1.0", + "update_link": "https://example.com/addons/test1.xpi", + "_comment_": "strictCompatibility: true", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/unsigned.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/unsigned.xpi new file mode 100644 index 0000000000..12a13f139b Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/unsigned.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/webext-implicit-id.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/webext-implicit-id.xpi new file mode 100644 index 0000000000..6b4abaa691 Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/webext-implicit-id.xpi differ diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js new file mode 100644 index 0000000000..626d9dea36 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -0,0 +1,1226 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* eslint no-unused-vars: ["error", {vars: "local", args: "none"}] */ + +if (!_TEST_NAME.includes("toolkit/mozapps/extensions/test/xpcshell/")) { + Assert.ok( + false, + "head_addons.js may not be loaded by tests outside of " + + "the add-on manager component." + ); +} + +const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; +const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; +const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; +const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required"; + +// Maximum error in file modification times. Some file systems don't store +// modification times exactly. As long as we are closer than this then it +// still passes. +const MAX_TIME_DIFFERENCE = 3000; + +// Time to reset file modified time relative to Date.now() so we can test that +// times are modified (10 hours old). +const MAKE_FILE_OLD_DIFFERENCE = 10 * 3600 * 1000; + +const { AddonManager, AddonManagerPrivate } = ChromeUtils.importESModule( + "resource://gre/modules/AddonManager.sys.mjs" +); +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +var { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); +var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { AddonRepository } = ChromeUtils.importESModule( + "resource://gre/modules/addons/AddonRepository.sys.mjs" +); + +var { AddonTestUtils, MockAsyncShutdown } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +ChromeUtils.defineModuleGetter( + this, + "HttpServer", + "resource://testing-common/httpd.js" +); +ChromeUtils.defineESModuleGetters(this, { + Blocklist: "resource://gre/modules/Blocklist.sys.mjs", + Extension: "resource://gre/modules/Extension.sys.mjs", + ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.sys.mjs", + ExtensionTestUtils: + "resource://testing-common/ExtensionXPCShellUtils.sys.mjs", + MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs", + MockRegistry: "resource://testing-common/MockRegistry.sys.mjs", + PromiseTestUtils: "resource://testing-common/PromiseTestUtils.sys.mjs", + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", + TestUtils: "resource://testing-common/TestUtils.sys.mjs", + setTimeout: "resource://gre/modules/Timer.sys.mjs", +}); + +XPCOMUtils.defineLazyServiceGetter( + this, + "aomStartup", + "@mozilla.org/addons/addon-manager-startup;1", + "amIAddonManagerStartup" +); + +const { + createAppInfo, + createHttpServer, + createTempWebExtensionFile, + getFileForAddon, + manuallyInstall, + manuallyUninstall, + overrideBuiltIns, + promiseAddonEvent, + promiseCompleteAllInstalls, + promiseCompleteInstall, + promiseConsoleOutput, + promiseFindAddonUpdates, + promiseInstallAllFiles, + promiseInstallFile, + promiseRestartManager, + promiseSetExtensionModifiedTime, + promiseShutdownManager, + promiseStartupManager, + promiseWebExtensionStartup, + promiseWriteProxyFileToDir, + registerDirectory, + setExtensionModifiedTime, + writeFilesToZip, +} = AddonTestUtils; + +// WebExtension wrapper for ease of testing +ExtensionTestUtils.init(this); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); + +XPCOMUtils.defineLazyGetter( + this, + "BOOTSTRAP_REASONS", + () => AddonManagerPrivate.BOOTSTRAP_REASONS +); + +function getReasonName(reason) { + for (let key of Object.keys(BOOTSTRAP_REASONS)) { + if (BOOTSTRAP_REASONS[key] == reason) { + return key; + } + } + throw new Error("This shouldn't happen."); +} + +Object.defineProperty(this, "gAppInfo", { + get() { + return AddonTestUtils.appInfo; + }, +}); + +Object.defineProperty(this, "gAddonStartup", { + get() { + return AddonTestUtils.addonStartup.clone(); + }, +}); + +Object.defineProperty(this, "gInternalManager", { + get() { + return AddonTestUtils.addonIntegrationService.QueryInterface( + Ci.nsITimerCallback + ); + }, +}); + +Object.defineProperty(this, "gProfD", { + get() { + return AddonTestUtils.profileDir.clone(); + }, +}); + +Object.defineProperty(this, "gTmpD", { + get() { + return AddonTestUtils.tempDir.clone(); + }, +}); + +Object.defineProperty(this, "gUseRealCertChecks", { + get() { + return AddonTestUtils.useRealCertChecks; + }, + set(val) { + AddonTestUtils.useRealCertChecks = val; + }, +}); + +Object.defineProperty(this, "TEST_UNPACKED", { + get() { + return AddonTestUtils.testUnpacked; + }, + set(val) { + AddonTestUtils.testUnpacked = val; + }, +}); + +const promiseAddonByID = AddonManager.getAddonByID; +const promiseAddonsByIDs = AddonManager.getAddonsByIDs; +const promiseAddonsByTypes = AddonManager.getAddonsByTypes; + +var gPort = null; + +var BootstrapMonitor = { + started: new Map(), + stopped: new Map(), + installed: new Map(), + uninstalled: new Map(), + + init() { + this.onEvent = this.onEvent.bind(this); + + AddonTestUtils.on("addon-manager-shutdown", this.onEvent); + AddonTestUtils.on("bootstrap-method", this.onEvent); + }, + + shutdownCheck() { + equal( + this.started.size, + 0, + "Should have no add-ons that were started but not shutdown" + ); + }, + + onEvent(msg, data) { + switch (msg) { + case "addon-manager-shutdown": + this.shutdownCheck(); + break; + case "bootstrap-method": + this.onBootstrapMethod(data.method, data.params, data.reason); + break; + } + }, + + onBootstrapMethod(method, params, reason) { + let { id } = params; + + info( + `Bootstrap method ${method} ${reason} for ${params.id} version ${params.version}` + ); + + if (method !== "install") { + this.checkInstalled(id); + } + + switch (method) { + case "install": + this.checkNotInstalled(id); + this.installed.set(id, { reason, params }); + this.uninstalled.delete(id); + break; + case "startup": + this.checkNotStarted(id); + this.started.set(id, { reason, params }); + this.stopped.delete(id); + break; + case "shutdown": + this.checkMatches("shutdown", "startup", params, this.started.get(id)); + this.checkStarted(id); + this.stopped.set(id, { reason, params }); + this.started.delete(id); + break; + case "uninstall": + this.checkMatches( + "uninstall", + "install", + params, + this.installed.get(id) + ); + this.uninstalled.set(id, { reason, params }); + this.installed.delete(id); + break; + case "update": + this.checkMatches("update", "install", params, this.installed.get(id)); + this.installed.set(id, { reason, params, method }); + break; + } + }, + + clear(id) { + this.installed.delete(id); + this.started.delete(id); + this.stopped.delete(id); + this.uninstalled.delete(id); + }, + + checkMatches(method, lastMethod, params, { params: lastParams } = {}) { + ok( + lastParams, + `Expecting matching ${lastMethod} call for add-on ${params.id} ${method} call` + ); + + if (method == "update") { + equal( + params.oldVersion, + lastParams.version, + "params.oldVersion should match last call" + ); + } else { + equal( + params.version, + lastParams.version, + "params.version should match last call" + ); + } + + if (method !== "update" && method !== "uninstall") { + equal( + params.resourceURI.spec, + lastParams.resourceURI.spec, + `params.resourceURI should match last call` + ); + + ok( + params.resourceURI.equals(lastParams.resourceURI), + `params.resourceURI should match: "${params.resourceURI.spec}" == "${lastParams.resourceURI.spec}"` + ); + } + }, + + checkStarted(id, version = undefined) { + let started = this.started.get(id); + ok(started, `Should have seen startup method call for ${id}`); + + if (version !== undefined) { + equal(started.params.version, version, "Expected version number"); + } + return started; + }, + + checkNotStarted(id) { + ok( + !this.started.has(id), + `Should not have seen startup method call for ${id}` + ); + }, + + checkInstalled(id, version = undefined) { + const installed = this.installed.get(id); + ok(installed, `Should have seen install call for ${id}`); + + if (version !== undefined) { + equal(installed.params.version, version, "Expected version number"); + } + + return installed; + }, + + checkUpdated(id, version = undefined) { + const installed = this.installed.get(id); + equal(installed.method, "update", `Should have seen update call for ${id}`); + + if (version !== undefined) { + equal(installed.params.version, version, "Expected version number"); + } + + return installed; + }, + + checkNotInstalled(id) { + ok( + !this.installed.has(id), + `Should not have seen install method call for ${id}` + ); + }, +}; + +function isNightlyChannel() { + var channel = Services.prefs.getCharPref("app.update.channel", "default"); + + return ( + channel != "aurora" && + channel != "beta" && + channel != "release" && + channel != "esr" + ); +} + +async function restartWithLocales(locales) { + Services.locale.requestedLocales = locales; + await promiseRestartManager(); +} + +function delay(msec) { + return new Promise(resolve => { + setTimeout(resolve, msec); + }); +} + +/** + * Returns a map of Addon objects for installed add-ons with the given + * IDs. The returned map contains a key for the ID of each add-on that + * is found. IDs for add-ons which do not exist are not present in the + * map. + * + * @param {sequence} ids + * The list of add-on IDs to get. + * @returns {Promise} + * Map of add-ons that were found. + */ +async function getAddons(ids) { + let addons = new Map(); + for (let addon of await AddonManager.getAddonsByIDs(ids)) { + if (addon) { + addons.set(addon.id, addon); + } + } + return addons; +} + +/** + * Checks that the given add-on has the given expected properties. + * + * @param {string} id + * The id of the add-on. + * @param {Addon?} addon + * The add-on object, or null if the add-on does not exist. + * @param {object?} expected + * An object containing the expected values for properties of the + * add-on, or null if the add-on is expected not to exist. + */ +function checkAddon(id, addon, expected) { + info(`Checking state of addon ${id}`); + + if (expected === null) { + ok(!addon, `Addon ${id} should not exist`); + } else { + ok(addon, `Addon ${id} should exist`); + for (let [key, value] of Object.entries(expected)) { + if (value instanceof Ci.nsIURI) { + equal( + addon[key] && addon[key].spec, + value.spec, + `Expected value of addon.${key}` + ); + } else { + deepEqual(addon[key], value, `Expected value of addon.${key}`); + } + } + } +} + +/** + * Tests that an add-on does appear in the crash report annotations, if + * crash reporting is enabled. The test will fail if the add-on is not in the + * annotation. + * @param aId + * The ID of the add-on + * @param aVersion + * The version of the add-on + */ +function do_check_in_crash_annotation(aId, aVersion) { + if (!AppConstants.MOZ_CRASHREPORTER) { + return; + } + + if (!("Add-ons" in gAppInfo.annotations)) { + Assert.ok(false, "Cannot find Add-ons entry in crash annotations"); + return; + } + + let addons = gAppInfo.annotations["Add-ons"].split(","); + Assert.ok( + addons.includes( + `${encodeURIComponent(aId)}:${encodeURIComponent(aVersion)}` + ) + ); +} + +/** + * Tests that an add-on does not appear in the crash report annotations, if + * crash reporting is enabled. The test will fail if the add-on is in the + * annotation. + * @param aId + * The ID of the add-on + * @param aVersion + * The version of the add-on + */ +function do_check_not_in_crash_annotation(aId, aVersion) { + if (!AppConstants.MOZ_CRASHREPORTER) { + return; + } + + if (!("Add-ons" in gAppInfo.annotations)) { + Assert.ok(true); + return; + } + + let addons = gAppInfo.annotations["Add-ons"].split(","); + Assert.ok( + !addons.includes( + `${encodeURIComponent(aId)}:${encodeURIComponent(aVersion)}` + ) + ); +} + +function do_get_file_hash(aFile, aAlgorithm) { + if (!aAlgorithm) { + aAlgorithm = "sha1"; + } + + let crypto = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + crypto.initWithString(aAlgorithm); + let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fis.init(aFile, -1, -1, false); + crypto.updateFromStream(fis, aFile.fileSize); + + // return the two-digit hexadecimal code for a byte + let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2); + + let binary = crypto.finish(false); + let hash = Array.from(binary, c => toHexString(c.charCodeAt(0))); + return aAlgorithm + ":" + hash.join(""); +} + +/** + * Returns an extension uri spec + * + * @param aProfileDir + * The extension install directory + * @return a uri spec pointing to the root of the extension + */ +function do_get_addon_root_uri(aProfileDir, aId) { + let path = aProfileDir.clone(); + path.append(aId); + if (!path.exists()) { + path.leafName += ".xpi"; + return "jar:" + Services.io.newFileURI(path).spec + "!/"; + } + return Services.io.newFileURI(path).spec; +} + +function do_get_expected_addon_name(aId) { + if (TEST_UNPACKED) { + return aId; + } + return aId + ".xpi"; +} + +/** + * Returns the file containing the add-on. For packed add-ons, this is + * an XPI file. For unpacked add-ons, it is the add-on's root directory. + * + * @param {Addon} addon + * @returns {nsIFile} + */ +function getAddonFile(addon) { + let uri = addon.getResourceURI(""); + if (uri instanceof Ci.nsIJARURI) { + uri = uri.JARFile; + } + return uri.QueryInterface(Ci.nsIFileURL).file; +} + +/** + * Check that an array of actual add-ons is the same as an array of + * expected add-ons. + * + * @param aActualAddons + * The array of actual add-ons to check. + * @param aExpectedAddons + * The array of expected add-ons to check against. + * @param aProperties + * An array of properties to check. + */ +function do_check_addons(aActualAddons, aExpectedAddons, aProperties) { + Assert.notEqual(aActualAddons, null); + Assert.equal(aActualAddons.length, aExpectedAddons.length); + for (let i = 0; i < aActualAddons.length; i++) { + do_check_addon(aActualAddons[i], aExpectedAddons[i], aProperties); + } +} + +/** + * Check that the actual add-on is the same as the expected add-on. + * + * @param aActualAddon + * The actual add-on to check. + * @param aExpectedAddon + * The expected add-on to check against. + * @param aProperties + * An array of properties to check. + */ +function do_check_addon(aActualAddon, aExpectedAddon, aProperties) { + Assert.notEqual(aActualAddon, null); + + aProperties.forEach(function (aProperty) { + let actualValue = aActualAddon[aProperty]; + let expectedValue = aExpectedAddon[aProperty]; + + // Check that all undefined expected properties are null on actual add-on + if (!(aProperty in aExpectedAddon)) { + if (actualValue !== undefined && actualValue !== null) { + do_throw( + "Unexpected defined/non-null property for add-on " + + aExpectedAddon.id + + " (addon[" + + aProperty + + "] = " + + actualValue.toSource() + + ")" + ); + } + + return; + } else if (expectedValue && !actualValue) { + do_throw( + "Missing property for add-on " + + aExpectedAddon.id + + ": expected addon[" + + aProperty + + "] = " + + expectedValue + ); + return; + } + + switch (aProperty) { + case "creator": + do_check_author(actualValue, expectedValue); + break; + + case "developers": + Assert.equal(actualValue.length, expectedValue.length); + for (let i = 0; i < actualValue.length; i++) { + do_check_author(actualValue[i], expectedValue[i]); + } + break; + + case "screenshots": + Assert.equal(actualValue.length, expectedValue.length); + for (let i = 0; i < actualValue.length; i++) { + do_check_screenshot(actualValue[i], expectedValue[i]); + } + break; + + case "sourceURI": + Assert.equal(actualValue.spec, expectedValue); + break; + + case "updateDate": + Assert.equal(actualValue.getTime(), expectedValue.getTime()); + break; + + case "compatibilityOverrides": + Assert.equal(actualValue.length, expectedValue.length); + for (let i = 0; i < actualValue.length; i++) { + do_check_compatibilityoverride(actualValue[i], expectedValue[i]); + } + break; + + case "icons": + do_check_icons(actualValue, expectedValue); + break; + + default: + if (actualValue !== expectedValue) { + do_throw( + "Failed for " + + aProperty + + " for add-on " + + aExpectedAddon.id + + " (" + + actualValue + + " === " + + expectedValue + + ")" + ); + } + } + }); +} + +/** + * Check that the actual author is the same as the expected author. + * + * @param aActual + * The actual author to check. + * @param aExpected + * The expected author to check against. + */ +function do_check_author(aActual, aExpected) { + Assert.equal(aActual.toString(), aExpected.name); + Assert.equal(aActual.name, aExpected.name); + Assert.equal(aActual.url, aExpected.url); +} + +/** + * Check that the actual screenshot is the same as the expected screenshot. + * + * @param aActual + * The actual screenshot to check. + * @param aExpected + * The expected screenshot to check against. + */ +function do_check_screenshot(aActual, aExpected) { + Assert.equal(aActual.toString(), aExpected.url); + Assert.equal(aActual.url, aExpected.url); + Assert.equal(aActual.width, aExpected.width); + Assert.equal(aActual.height, aExpected.height); + Assert.equal(aActual.thumbnailURL, aExpected.thumbnailURL); + Assert.equal(aActual.thumbnailWidth, aExpected.thumbnailWidth); + Assert.equal(aActual.thumbnailHeight, aExpected.thumbnailHeight); + Assert.equal(aActual.caption, aExpected.caption); +} + +/** + * Check that the actual compatibility override is the same as the expected + * compatibility override. + * + * @param aAction + * The actual compatibility override to check. + * @param aExpected + * The expected compatibility override to check against. + */ +function do_check_compatibilityoverride(aActual, aExpected) { + Assert.equal(aActual.type, aExpected.type); + Assert.equal(aActual.minVersion, aExpected.minVersion); + Assert.equal(aActual.maxVersion, aExpected.maxVersion); + Assert.equal(aActual.appID, aExpected.appID); + Assert.equal(aActual.appMinVersion, aExpected.appMinVersion); + Assert.equal(aActual.appMaxVersion, aExpected.appMaxVersion); +} + +function do_check_icons(aActual, aExpected) { + for (var size in aExpected) { + Assert.equal(aActual[size], aExpected[size]); + } +} + +function isThemeInAddonsList(aDir, aId) { + return AddonTestUtils.addonsList.hasTheme(aDir, aId); +} + +function isExtensionInBootstrappedList(aDir, aId) { + return AddonTestUtils.addonsList.hasExtension(aDir, aId); +} + +/** + * Writes a manifest.json manifest into an extension using the properties passed + * in a JS object. + * + * @param aManifest + * The data to write + * @param aDir + * The install directory to add the extension to + * @param aId + * An optional string to override the default installation aId + * @return A file pointing to where the extension was installed + */ +function promiseWriteWebManifestForExtension(aData, aDir, aId) { + let files = { + "manifest.json": JSON.stringify(aData), + }; + if (!aId) { + aId = + aData?.browser_specific_settings?.gecko?.id || + aData?.applications?.gecko?.id; + } + return AddonTestUtils.promiseWriteFilesToExtension(aDir.path, aId, files); +} + +function hasFlag(aBits, aFlag) { + return (aBits & aFlag) != 0; +} + +class EventChecker { + constructor(options) { + this.expectedEvents = options.addonEvents || {}; + this.expectedInstalls = options.installEvents || null; + this.ignorePlugins = options.ignorePlugins || false; + + this.finished = new Promise(resolve => { + this.resolveFinished = resolve; + }); + + AddonManager.addAddonListener(this); + if (this.expectedInstalls) { + AddonManager.addInstallListener(this); + } + } + + cleanup() { + AddonManager.removeAddonListener(this); + if (this.expectedInstalls) { + AddonManager.removeInstallListener(this); + } + } + + checkValue(prop, value, flagName) { + if (Array.isArray(flagName)) { + let names = flagName.map(name => `AddonManager.${name}`); + + Assert.ok( + flagName.map(name => AddonManager[name]).includes(value), + `${prop} value \`${value}\` should be one of [${names.join(", ")}` + ); + } else { + Assert.equal( + value, + AddonManager[flagName], + `${prop} should have value AddonManager.${flagName}` + ); + } + } + + checkFlag(prop, value, flagName) { + Assert.equal( + value & AddonManager[flagName], + AddonManager[flagName], + `${prop} should have flag AddonManager.${flagName}` + ); + } + + checkNoFlag(prop, value, flagName) { + Assert.ok( + !(value & AddonManager[flagName]), + `${prop} should not have flag AddonManager.${flagName}` + ); + } + + checkComplete() { + if (this.expectedInstalls && this.expectedInstalls.length) { + return; + } + + if (Object.values(this.expectedEvents).some(events => events.length)) { + return; + } + + info("Test complete"); + this.cleanup(); + this.resolveFinished(); + } + + ensureComplete() { + this.cleanup(); + + for (let [id, events] of Object.entries(this.expectedEvents)) { + Assert.equal( + events.length, + 0, + `Should have no remaining events for ${id}` + ); + } + if (this.expectedInstalls) { + Assert.deepEqual( + this.expectedInstalls, + [], + "Should have no remaining install events" + ); + } + } + + // Add-on listener events + getExpectedEvent(aId) { + if (!(aId in this.expectedEvents)) { + return null; + } + + let events = this.expectedEvents[aId]; + Assert.ok(!!events.length, `Should be expecting events for ${aId}`); + + return events.shift(); + } + + checkAddonEvent(event, addon, details = {}) { + info(`Got event "${event}" for add-on ${addon.id}`); + + if ("requiresRestart" in details) { + Assert.equal( + details.requiresRestart, + false, + "requiresRestart should always be false" + ); + } + + let expected = this.getExpectedEvent(addon.id); + if (!expected) { + return undefined; + } + + Assert.equal( + expected.event, + event, + `Expecting event "${expected.event}" got "${event}"` + ); + + for (let prop of ["properties"]) { + if (prop in expected) { + Assert.deepEqual( + expected[prop], + details[prop], + `Expected value for ${prop}` + ); + } + } + + this.checkComplete(); + + if ("returnValue" in expected) { + return expected.returnValue; + } + return undefined; + } + + onPropertyChanged(addon, properties) { + return this.checkAddonEvent("onPropertyChanged", addon, { properties }); + } + + onEnabling(addon, requiresRestart) { + let result = this.checkAddonEvent("onEnabling", addon, { requiresRestart }); + + this.checkNoFlag("addon.permissions", addon.permissions, "PERM_CAN_ENABLE"); + + return result; + } + + onEnabled(addon) { + let result = this.checkAddonEvent("onEnabled", addon); + + this.checkNoFlag("addon.permissions", addon.permissions, "PERM_CAN_ENABLE"); + + return result; + } + + onDisabling(addon, requiresRestart) { + let result = this.checkAddonEvent("onDisabling", addon, { + requiresRestart, + }); + + this.checkNoFlag( + "addon.permissions", + addon.permissions, + "PERM_CAN_DISABLE" + ); + return result; + } + + onDisabled(addon) { + let result = this.checkAddonEvent("onDisabled", addon); + + this.checkNoFlag( + "addon.permissions", + addon.permissions, + "PERM_CAN_DISABLE" + ); + + return result; + } + + onInstalling(addon, requiresRestart) { + return this.checkAddonEvent("onInstalling", addon, { requiresRestart }); + } + + onInstalled(addon) { + return this.checkAddonEvent("onInstalled", addon); + } + + onUninstalling(addon, requiresRestart) { + return this.checkAddonEvent("onUninstalling", addon); + } + + onUninstalled(addon) { + return this.checkAddonEvent("onUninstalled", addon); + } + + onOperationCancelled(addon) { + return this.checkAddonEvent("onOperationCancelled", addon); + } + + // Install listener events. + checkInstall(event, install, details = {}) { + // Lazy initialization of the plugin host means we can get spurious + // install events for plugins. If we're not looking for plugin + // installs, ignore them completely. If we *are* looking for plugin + // installs, the onus is on the individual test to ensure it waits + // for the plugin host to have done its initial work. + if (this.ignorePlugins && install.type == "plugin") { + info(`Ignoring install event for plugin ${install.id}`); + return undefined; + } + info(`Got install event "${event}"`); + + let expected = this.expectedInstalls.shift(); + Assert.ok(expected, "Should be expecting install event"); + + Assert.equal( + expected.event, + event, + "Should be expecting onExternalInstall event" + ); + + if ("state" in details) { + this.checkValue("install.state", install.state, details.state); + } + + this.checkComplete(); + + if ("callback" in expected) { + expected.callback(install); + } + + if ("returnValue" in expected) { + return expected.returnValue; + } + return undefined; + } + + onNewInstall(install) { + let result = this.checkInstall("onNewInstall", install, { + state: ["STATE_DOWNLOADED", "STATE_DOWNLOAD_FAILED", "STATE_AVAILABLE"], + }); + + if (install.state != AddonManager.STATE_DOWNLOAD_FAILED) { + Assert.equal(install.error, 0, "Should have no error"); + } else { + Assert.notEqual(install.error, 0, "Should have error"); + } + + return result; + } + + onDownloadStarted(install) { + return this.checkInstall("onDownloadStarted", install, { + state: "STATE_DOWNLOADING", + error: 0, + }); + } + + onDownloadEnded(install) { + return this.checkInstall("onDownloadEnded", install, { + state: "STATE_DOWNLOADED", + error: 0, + }); + } + + onDownloadFailed(install) { + return this.checkInstall("onDownloadFailed", install, { + state: "STATE_FAILED", + }); + } + + onDownloadCancelled(install) { + return this.checkInstall("onDownloadCancelled", install, { + state: "STATE_CANCELLED", + error: 0, + }); + } + + onInstallStarted(install) { + return this.checkInstall("onInstallStarted", install, { + state: "STATE_INSTALLING", + error: 0, + }); + } + + onInstallEnded(install, newAddon) { + return this.checkInstall("onInstallEnded", install, { + state: "STATE_INSTALLED", + error: 0, + }); + } + + onInstallFailed(install) { + return this.checkInstall("onInstallFailed", install, { + state: "STATE_FAILED", + }); + } + + onInstallCancelled(install) { + // If the install was cancelled by a listener returning false from + // onInstallStarted, then the state will revert to STATE_DOWNLOADED. + return this.checkInstall("onInstallCancelled", install, { + state: ["STATE_CANCELED", "STATE_DOWNLOADED"], + error: 0, + }); + } + + onExternalInstall(addon, existingAddon, requiresRestart) { + if (this.ignorePlugins && addon.type == "plugin") { + info(`Ignoring install event for plugin ${addon.id}`); + return undefined; + } + let expected = this.expectedInstalls.shift(); + Assert.ok(expected, "Should be expecting install event"); + + Assert.equal( + expected.event, + "onExternalInstall", + "Should be expecting onExternalInstall event" + ); + Assert.ok(!requiresRestart, "Should never require restart"); + + this.checkComplete(); + if ("returnValue" in expected) { + return expected.returnValue; + } + return undefined; + } +} + +/** + * Run the giving callback function, and expect the given set of add-on + * and install listener events to be emitted, and returns a promise + * which resolves when they have all been observed. + * + * If `callback` returns a promise, all events are expected to be + * observed by the time the promise resolves. If not, simply waits for + * all events to be observed before resolving the returned promise. + * + * @param {object} details + * @param {function} callback + * @returns {Promise} + */ +/* exported expectEvents */ +async function expectEvents(details, callback) { + let checker = new EventChecker(details); + + try { + let result = callback(); + + if ( + result && + typeof result === "object" && + typeof result.then === "function" + ) { + result = await result; + checker.ensureComplete(); + } else { + await checker.finished; + } + + return result; + } catch (e) { + do_throw(e); + return undefined; + } +} + +const EXTENSIONS_DB = "extensions.json"; +var gExtensionsJSON = gProfD.clone(); +gExtensionsJSON.append(EXTENSIONS_DB); + +async function promiseInstallWebExtension(aData) { + let addonFile = createTempWebExtensionFile(aData); + + let { addon } = await promiseInstallFile(addonFile); + return addon; +} + +// By default use strict compatibility +Services.prefs.setBoolPref("extensions.strictCompatibility", true); + +// Ensure signature checks are enabled by default +Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); + +Services.prefs.setBoolPref("extensions.experiments.enabled", true); + +// Copies blocklistFile (an nsIFile) to gProfD/blocklist.xml. +function copyBlocklistToProfile(blocklistFile) { + var dest = gProfD.clone(); + dest.append("blocklist.xml"); + if (dest.exists()) { + dest.remove(false); + } + blocklistFile.copyTo(gProfD, "blocklist.xml"); + dest.lastModifiedTime = Date.now(); +} + +async function mockGfxBlocklistItemsFromDisk(path) { + Cu.importGlobalProperties(["fetch"]); + let response = await fetch(Services.io.newFileURI(do_get_file(path)).spec); + let json = await response.json(); + return mockGfxBlocklistItems(json); +} + +async function mockGfxBlocklistItems(items) { + const { generateUUID } = Services.uuid; + const { BlocklistPrivate } = ChromeUtils.importESModule( + "resource://gre/modules/Blocklist.sys.mjs" + ); + const client = RemoteSettings("gfx", { + bucketName: "blocklists", + }); + const records = items.map(item => { + if (item.id && item.last_modified) { + return item; + } + return { + id: generateUUID().toString().replace(/[{}]/g, ""), + last_modified: Date.now(), + ...item, + }; + }); + const collectionTimestamp = Math.max(...records.map(r => r.last_modified)); + await client.db.importChanges({}, collectionTimestamp, records, { + clear: true, + }); + let rv = await BlocklistPrivate.GfxBlocklistRS.checkForEntries(); + return rv; +} + +/** + * Change the schema version of the JSON extensions database + */ +async function changeXPIDBVersion(aNewVersion) { + let json = await IOUtils.readJSON(gExtensionsJSON.path); + json.schemaVersion = aNewVersion; + await IOUtils.writeJSON(gExtensionsJSON.path, json); +} + +async function setInitialState(addon, initialState) { + if (initialState.userDisabled) { + await addon.disable(); + } else if (initialState.userDisabled === false) { + await addon.enable(); + } +} + +async function setupBuiltinExtension(extensionData, location = "ext-test") { + let xpi = await AddonTestUtils.createTempWebExtensionFile(extensionData); + + // The built-in location requires a resource: URL that maps to a + // jar: or file: URL. This would typically be something bundled + // into omni.ja but for testing we just use a temp file. + let base = Services.io.newURI(`jar:file:${xpi.path}!/`); + let resProto = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProto.setSubstitution(location, base); +} + +async function installBuiltinExtension(extensionData, waitForStartup = true) { + await setupBuiltinExtension(extensionData); + + let id = + extensionData.manifest?.browser_specific_settings?.gecko?.id || + extensionData.manifest?.applications?.gecko?.id; + let wrapper = ExtensionTestUtils.expectExtension(id); + await AddonManager.installBuiltinAddon("resource://ext-test/"); + if (waitForStartup) { + await wrapper.awaitStartup(); + } + return wrapper; +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_amremotesettings.js b/toolkit/mozapps/extensions/test/xpcshell/head_amremotesettings.js new file mode 100644 index 0000000000..36741736fa --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/head_amremotesettings.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +ChromeUtils.defineESModuleGetters(this, { + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", +}); + +async function setAndEmitFakeRemoteSettingsData( + data, + expectClientInitialized = true +) { + const { AMRemoteSettings } = ChromeUtils.importESModule( + "resource://gre/modules/AddonManager.sys.mjs" + ); + let client; + if (expectClientInitialized) { + ok(AMRemoteSettings.client, "Got a remote settings client"); + ok(AMRemoteSettings.onSync, "Got a remote settings 'sync' event handler"); + client = AMRemoteSettings.client; + } else { + // No client is expected to exist, and so we create one to inject the expected data + // into the RemoteSettings db. + client = new RemoteSettings(AMRemoteSettings.RS_COLLECTION); + } + + await client.db.clear(); + if (data.length) { + await client.db.importChanges({}, Date.now(), data); + } + await client.emit("sync", { data: {} }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_cert_handling.js b/toolkit/mozapps/extensions/test/xpcshell/head_cert_handling.js new file mode 100644 index 0000000000..08c41a8c7e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/head_cert_handling.js @@ -0,0 +1,33 @@ +// Helpers for handling certs. +// These are taken from +// https://searchfox.org/mozilla-central/rev/36aa22c7ea92bd3cf7910774004fff7e63341cf5/security/manager/ssl/tests/unit/head_psm.js +// but we don't want to drag that file in here because +// - it conflicts with `head_addons.js`. +// - it has a lot of extra code we don't need. +// So dupe relevant code here. + +// This file will be included along with head_addons.js, use its globals. +/* import-globals-from head_addons.js */ + +"use strict"; + +function readFile(file) { + let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(file, -1, 0, 0); + let available = fstream.available(); + let data = + available > 0 ? NetUtil.readInputStreamToString(fstream, available) : ""; + fstream.close(); + return data; +} + +function loadCertChain(prefix, names) { + let chain = []; + for (let name of names) { + let filename = `${prefix}_${name}.pem`; + chain.push(readFile(do_get_file(filename))); + } + return chain; +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_compat.js b/toolkit/mozapps/extensions/test/xpcshell/head_compat.js new file mode 100644 index 0000000000..70e084d307 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/head_compat.js @@ -0,0 +1,49 @@ +// +// This file provides helpers for tests of addons that use strictCompatibility. +// Since WebExtensions cannot opt out of strictCompatibility, we add a +// simple extension loader that lets tests directly set AddonInternal +// properties (including strictCompatibility) +// + +/* import-globals-from head_addons.js */ + +const MANIFEST = "compat_manifest.json"; + +AddonManager.addExternalExtensionLoader({ + name: "compat-test", + manifestFile: MANIFEST, + async loadManifest(pkg) { + // XPIDatabase.jsm gets unloaded in AddonTestUtils when the + // addon manager is restarted. Work around that by just importing + // it every time we need to create an AddonInternal. + const { AddonInternal } = ChromeUtils.import( + "resource://gre/modules/addons/XPIDatabase.jsm" + ); + let addon = new AddonInternal(); + let manifest = JSON.parse(await pkg.readString(MANIFEST)); + Object.assign(addon, manifest); + return addon; + }, + loadScope(addon, file) { + return { + install() {}, + uninstall() {}, + startup() {}, + shutdonw() {}, + }; + }, +}); + +const DEFAULTS = { + defaultLocale: {}, + locales: [], + targetPlatforms: [], + type: "extension", + version: "1.0", +}; + +function createAddon(manifest) { + return AddonTestUtils.createTempXPIFile({ + [MANIFEST]: Object.assign({}, DEFAULTS, manifest), + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_sideload.js b/toolkit/mozapps/extensions/test/xpcshell/head_sideload.js new file mode 100644 index 0000000000..8ff3f2f072 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/head_sideload.js @@ -0,0 +1,76 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +/* import-globals-from head_addons.js */ + +// Enable all scopes. +Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_ALL); +// Setting this to all enables the same behavior as before disabling sideloading. +// We reset this later after doing some legacy sideloading. +Services.prefs.setIntPref("extensions.sideloadScopes", AddonManager.SCOPE_ALL); +// AddonTestUtils sets this to zero, we need the default value. +Services.prefs.clearUserPref("extensions.autoDisableScopes"); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + +function getID(n) { + return `${n}@tests.mozilla.org`; +} +function initialVersion(n) { + return `${n}.0`; +} + +// Setup some common extension locations, one in each scope. + +// SCOPE_SYSTEM +const globalDir = gProfD.clone(); +globalDir.append("app-system-share"); +globalDir.append(gAppInfo.ID); +registerDirectory("XRESysSExtPD", globalDir.parent); + +// SCOPE_USER +const userDir = gProfD.clone(); +userDir.append("app-system-user"); +userDir.append(gAppInfo.ID); +registerDirectory("XREUSysExt", userDir.parent); + +// SCOPE_APPLICATION +const addonAppDir = gProfD.clone(); +addonAppDir.append("app-global"); +addonAppDir.append("extensions"); +registerDirectory("XREAddonAppDir", addonAppDir.parent); + +// SCOPE_PROFILE +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +const scopeDirectories = { + global: globalDir, + user: userDir, + app: addonAppDir, + profile: profileDir, +}; + +const scopeToDir = new Map([ + [AddonManager.SCOPE_SYSTEM, globalDir], + [AddonManager.SCOPE_USER, userDir], + [AddonManager.SCOPE_APPLICATION, addonAppDir], + [AddonManager.SCOPE_PROFILE, profileDir], +]); + +async function createWebExtension(id, version, dir) { + let xpi = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version, + browser_specific_settings: { gecko: { id } }, + }, + }); + await AddonTestUtils.manuallyInstall(xpi, dir); +} + +function check_startup_changes(aType, aIds) { + let changes = AddonManager.getStartupChanges(aType); + changes = changes.filter(aEl => /@tests.mozilla.org$/.test(aEl)); + + Assert.deepEqual([...aIds].sort(), changes.sort()); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js new file mode 100644 index 0000000000..4cb591dd56 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js @@ -0,0 +1,472 @@ +/* import-globals-from head_addons.js */ + +const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; +const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url"; +const PREF_SYSTEM_ADDON_UPDATE_ENABLED = + "extensions.systemAddon.update.enabled"; + +// See bug 1507255 +Services.prefs.setBoolPref("media.gmp-manager.updateEnabled", true); + +function root(server) { + let { primaryScheme, primaryHost, primaryPort } = server.identity; + return `${primaryScheme}://${primaryHost}:${primaryPort}/data`; +} + +XPCOMUtils.defineLazyGetter(this, "testserver", () => { + let server = new HttpServer(); + server.start(); + Services.prefs.setCharPref( + PREF_SYSTEM_ADDON_UPDATE_URL, + `${root(server)}/update.xml` + ); + return server; +}); + +async function serveSystemUpdate(xml, perform_update) { + testserver.registerPathHandler("/data/update.xml", (request, response) => { + response.write(xml); + }); + + try { + await perform_update(); + } finally { + testserver.registerPathHandler("/data/update.xml", null); + } +} + +// Runs an update check making it use the passed in xml string. Uses the direct +// call to the update function so we get rejections on failure. +async function installSystemAddons(xml, waitIDs = []) { + info("Triggering system add-on update check."); + + await serveSystemUpdate( + xml, + async function () { + let { XPIProvider } = ChromeUtils.import( + "resource://gre/modules/addons/XPIProvider.jsm" + ); + await Promise.all([ + XPIProvider.updateSystemAddons(), + ...waitIDs.map(id => promiseWebExtensionStartup(id)), + ]); + }, + testserver + ); +} + +// Runs a full add-on update check which will in some cases do a system add-on +// update check. Always succeeds. +async function updateAllSystemAddons(xml) { + info("Triggering full add-on update check."); + + await serveSystemUpdate( + xml, + function () { + return new Promise(resolve => { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver( + observer, + "addons-background-update-complete" + ); + + resolve(); + }, "addons-background-update-complete"); + + // Trigger the background update timer handler + gInternalManager.notify(null); + }); + }, + testserver + ); +} + +// Builds an update.xml file for an update check based on the data passed. +function buildSystemAddonUpdates(addons) { + let xml = `\n\n\n`; + if (addons) { + xml += ` \n`; + for (let addon of addons) { + if (addon.xpi) { + testserver.registerFile(`/data/${addon.path}`, addon.xpi); + } + + xml += ` \n`; + } + xml += ` \n`; + } + xml += `\n`; + + return xml; +} + +let _systemXPIs = new Map(); +function getSystemAddonXPI(num, version) { + let key = `${num}:${version}`; + if (!_systemXPIs.has(key)) { + _systemXPIs.set( + key, + AddonTestUtils.createTempWebExtensionFile({ + manifest: { + name: `System Add-on ${num}`, + version, + browser_specific_settings: { + gecko: { + id: `system${num}@tests.mozilla.org`, + }, + }, + }, + }) + ); + } + return _systemXPIs.get(key); +} + +async function initSystemAddonDirs() { + let hiddenSystemAddonDir = FileUtils.getDir( + "ProfD", + ["sysfeatures", "hidden"], + true + ); + let system1_1 = await getSystemAddonXPI(1, "1.0"); + system1_1.copyTo(hiddenSystemAddonDir, "system1@tests.mozilla.org.xpi"); + + let system2_1 = await getSystemAddonXPI(2, "1.0"); + system2_1.copyTo(hiddenSystemAddonDir, "system2@tests.mozilla.org.xpi"); + + let prefilledSystemAddonDir = FileUtils.getDir( + "ProfD", + ["sysfeatures", "prefilled"], + true + ); + let system2_2 = await getSystemAddonXPI(2, "2.0"); + system2_2.copyTo(prefilledSystemAddonDir, "system2@tests.mozilla.org.xpi"); + let system3_2 = await getSystemAddonXPI(3, "2.0"); + system3_2.copyTo(prefilledSystemAddonDir, "system3@tests.mozilla.org.xpi"); +} + +/** + * Returns current system add-on update directory (stored in pref). + */ +function getCurrentSystemAddonUpdatesDir() { + const updatesDir = FileUtils.getDir("ProfD", ["features"], false); + let dir = updatesDir.clone(); + let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET)); + dir.append(set.directory); + return dir; +} + +/** + * Removes all files from system add-on update directory. + */ +function clearSystemAddonUpdatesDir() { + const updatesDir = FileUtils.getDir("ProfD", ["features"], false); + // Delete any existing directories + if (updatesDir.exists()) { + updatesDir.remove(true); + } + + Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET); +} + +registerCleanupFunction(() => { + clearSystemAddonUpdatesDir(); +}); + +/** + * Installs a known set of add-ons into the system add-on update directory. + */ +async function buildPrefilledUpdatesDir() { + clearSystemAddonUpdatesDir(); + + // Build the test set + let dir = FileUtils.getDir("ProfD", ["features", "prefilled"], true); + + let xpi = await getSystemAddonXPI(2, "2.0"); + xpi.copyTo(dir, "system2@tests.mozilla.org.xpi"); + + xpi = await getSystemAddonXPI(3, "2.0"); + xpi.copyTo(dir, "system3@tests.mozilla.org.xpi"); + + // Mark these in the past so the startup file scan notices when files have changed properly + FileUtils.getFile("ProfD", [ + "features", + "prefilled", + "system2@tests.mozilla.org.xpi", + ]).lastModifiedTime -= 10000; + FileUtils.getFile("ProfD", [ + "features", + "prefilled", + "system3@tests.mozilla.org.xpi", + ]).lastModifiedTime -= 10000; + + Services.prefs.setCharPref( + PREF_SYSTEM_ADDON_SET, + JSON.stringify({ + schema: 1, + directory: dir.leafName, + addons: { + "system2@tests.mozilla.org": { + version: "2.0", + }, + "system3@tests.mozilla.org": { + version: "2.0", + }, + }, + }) + ); +} + +/** + * Check currently installed ssystem add-ons against a set of conditions. + * + * @param {Array} conditions - an array of objects of the form { isUpgrade: false, version: null} + * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory) + */ +async function checkInstalledSystemAddons(conditions, distroDir) { + for (let i = 0; i < conditions.length; i++) { + let condition = conditions[i]; + let id = "system" + (i + 1) + "@tests.mozilla.org"; + let addon = await promiseAddonByID(id); + + if (!("isUpgrade" in condition) || !("version" in condition)) { + throw Error("condition must contain isUpgrade and version"); + } + let isUpgrade = conditions[i].isUpgrade; + let version = conditions[i].version; + + let expectedDir = isUpgrade ? getCurrentSystemAddonUpdatesDir() : distroDir; + + if (version) { + info(`Checking state of add-on ${id}, expecting version ${version}`); + + // Add-on should be installed + Assert.notEqual(addon, null); + Assert.equal(addon.version, version); + Assert.ok(addon.isActive); + Assert.ok(!addon.foreignInstall); + Assert.ok(addon.hidden); + Assert.ok(addon.isSystem); + + // Verify the add-ons file is in the right place + let file = expectedDir.clone(); + file.append(id + ".xpi"); + Assert.ok(file.exists()); + Assert.ok(file.isFile()); + + let uri = addon.getResourceURI(); + if (uri instanceof Ci.nsIJARURI) { + uri = uri.JARFile; + } + + Assert.ok(uri instanceof Ci.nsIFileURL); + Assert.equal(uri.file.path, file.path); + + if (isUpgrade) { + Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM); + } + } else { + info(`Checking state of add-on ${id}, expecting it to be missing`); + + if (isUpgrade) { + // Add-on should not be installed + Assert.equal(addon, null); + } + } + } +} + +/** + * Returns all system add-on updates directories. + */ +async function getSystemAddonDirectories() { + const updatesDir = FileUtils.getDir("ProfD", ["features"], false); + let subdirs = []; + + if (await IOUtils.exists(updatesDir.path)) { + for (const child of await IOUtils.getChildren(updatesDir.path)) { + const stat = await IOUtils.stat(child); + if (stat.type === "directory") { + subdirs.push(child); + } + } + } + + return subdirs; +} + +/** + * Sets up initial system add-on update conditions. + * + * @param {Object} setup - an object containing a setup function and an array of objects + * of the form {isUpgrade: false, version: null} + * + * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory) + */ +async function setupSystemAddonConditions(setup, distroDir) { + info("Clearing existing database."); + Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET); + distroDir.leafName = "empty"; + + let updateList = []; + await overrideBuiltIns({ system: updateList }); + await promiseStartupManager(); + await promiseShutdownManager(); + + info("Setting up conditions."); + await setup.setup(); + + if (distroDir) { + if (distroDir.path.endsWith("hidden")) { + updateList = ["system1@tests.mozilla.org", "system2@tests.mozilla.org"]; + } else if (distroDir.path.endsWith("prefilled")) { + updateList = ["system2@tests.mozilla.org", "system3@tests.mozilla.org"]; + } + } + await overrideBuiltIns({ system: updateList }); + + let startupPromises = setup.initialState.map((item, i) => + item.version + ? promiseWebExtensionStartup(`system${i + 1}@tests.mozilla.org`) + : null + ); + await Promise.all([promiseStartupManager(), ...startupPromises]); + + // Make sure the initial state is correct + info("Checking initial state."); + await checkInstalledSystemAddons(setup.initialState, distroDir); +} + +/** + * Verify state of system add-ons after installation. + * + * @param {Array} initialState - an array of objects of the form {isUpgrade: false, version: null} + * @param {Array} finalState - an array of objects of the form {isUpgrade: false, version: null} + * @param {Boolean} alreadyUpgraded - whether a restartless upgrade has already been performed. + * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory) + */ +async function verifySystemAddonState( + initialState, + finalState = undefined, + alreadyUpgraded = false, + distroDir +) { + let expectedDirs = 0; + + // If the initial state was using the profile set then that directory will + // still exist. + + if (initialState.some(a => a.isUpgrade)) { + expectedDirs++; + } + + if (finalState == undefined) { + finalState = initialState; + } else if (finalState.some(a => a.isUpgrade)) { + // If the new state is using the profile then that directory will exist. + expectedDirs++; + } + + // Since upgrades are restartless now, the previous update dir hasn't been removed. + if (alreadyUpgraded) { + expectedDirs++; + } + + info("Checking final state."); + + let dirs = await getSystemAddonDirectories(); + Assert.equal(dirs.length, expectedDirs); + + await checkInstalledSystemAddons(...finalState, distroDir); + + // Check that the new state is active after a restart + await promiseShutdownManager(); + + let updateList = []; + + if (distroDir) { + if (distroDir.path.endsWith("hidden")) { + updateList = ["system1@tests.mozilla.org", "system2@tests.mozilla.org"]; + } else if (distroDir.path.endsWith("prefilled")) { + updateList = ["system2@tests.mozilla.org", "system3@tests.mozilla.org"]; + } + } + await overrideBuiltIns({ system: updateList }); + await promiseStartupManager(); + await checkInstalledSystemAddons(finalState, distroDir); +} + +/** + * Run system add-on tests and compare the results against a set of expected conditions. + * + * @param {String} setupName - name of the current setup conditions. + * @param {Object} setup - Defines the set of initial conditions to run each test against. Each should + * define the following properties: + * setup: A task to setup the profile into the initial state. + * initialState: The initial expected system add-on state after setup has run. + * @param {Array} test - The test to run. Each test must define an updateList or test. The following + * properties are used: + * updateList: The set of add-ons the server should respond with. + * test: A function to run to perform the update check (replaces + * updateList) + * fails: An optional regex property, if present the update check is expected to + * fail. + * finalState: An optional property, the expected final state of system add-ons, + * if missing the test condition's initialState is used. + * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory) + */ + +async function execSystemAddonTest(setupName, setup, test, distroDir) { + // Initial system addon conditions need system signature + AddonTestUtils.usePrivilegedSignatures = "system"; + await setupSystemAddonConditions(setup, distroDir); + + // The test may define what signature to use when running the test + if (test.usePrivilegedSignatures != undefined) { + AddonTestUtils.usePrivilegedSignatures = test.usePrivilegedSignatures; + } + + function runTest() { + if ("test" in test) { + return test.test(); + } + let xml = buildSystemAddonUpdates(test.updateList); + let ids = (test.updateList || []).map(item => item.id); + return installSystemAddons(xml, ids); + } + + if (test.fails) { + await Assert.rejects(runTest(), test.fails); + } else { + await runTest(); + } + + // some tests have a different expected combination of default + // and updated add-ons. + if (test.finalState && setupName in test.finalState) { + await verifySystemAddonState( + setup.initialState, + test.finalState[setupName], + false, + distroDir + ); + } else { + await verifySystemAddonState( + setup.initialState, + undefined, + false, + distroDir + ); + } + + await promiseShutdownManager(); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_unpack.js b/toolkit/mozapps/extensions/test/xpcshell/head_unpack.js new file mode 100644 index 0000000000..909dc1da2f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/head_unpack.js @@ -0,0 +1,3 @@ +/* globals Services, TEST_UNPACKED: true */ +/* exported TEST_UNPACKED */ +TEST_UNPACKED = true; diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js new file mode 100644 index 0000000000..4007ec6988 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js @@ -0,0 +1,120 @@ +// Appease eslint. +/* import-globals-from ../head_addons.js */ + +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +// Initializing and asserting the expected telemetry is currently conditioned +// on this const. +// TODO(Bug 1752139) remove this along with initializing and asserting the expected +// telemetry also for android build, once `Services.fog.testResetFOG()` is implemented +// for Android builds. +const IS_ANDROID_BUILD = AppConstants.platform === "android"; +const IS_FOG_RESET_SUPPORTED = !IS_ANDROID_BUILD; +const DUMMY_TIME = 939420000000; // new Date(1999, 9, 9) +const DUMMY_STRING = "GleanDummyString"; +let gleanEventCount = 0; + +function _resetMetric(gleanMetric) { + let value = gleanMetric.testGetValue(); + if (value === undefined) { + return; // Never initialized, nothing to reset. + } + if (gleanMetric instanceof Ci.nsIGleanDatetime) { + gleanMetric.set(DUMMY_TIME * 1000); + } else if (gleanMetric instanceof Ci.nsIGleanString) { + gleanMetric.set(DUMMY_STRING); + } else if (gleanMetric instanceof Ci.nsIGleanEvent) { + // NOTE: this doesn't work when there is more than one event; + // for now we assume that there is only one: addonBlockChange. + // Cannot overwrite, so just store the current list length. + gleanEventCount = value.length; + } else { + throw new Error("Unsupported Glean metric type - cannot reset"); + } +} + +function testGetValue(gleanMetric) { + let value = gleanMetric.testGetValue(); + if (value === undefined || IS_FOG_RESET_SUPPORTED) { + return value; + } + if (gleanMetric instanceof Ci.nsIGleanDatetime) { + return value.getTime() === DUMMY_TIME ? undefined : value; + } + if (gleanMetric instanceof Ci.nsIGleanString) { + return value === DUMMY_STRING ? undefined : value; + } + if (gleanMetric instanceof Ci.nsIGleanEvent) { + // NOTE: this doesn't work when there is more than one event; + // for now we assume that there is only one: addonBlockChange. + value = value.slice(gleanEventCount); + return value.length ? value : undefined; + } + throw new Error("Unsupported Glean metric type"); +} + +function resetBlocklistTelemetry() { + if (IS_FOG_RESET_SUPPORTED) { + Services.fog.testResetFOG(); + return; + } + // TODO bug 1752139: fix testResetFOG and remove workarounds. + _resetMetric(Glean.blocklist.addonBlockChange); + _resetMetric(Glean.blocklist.lastModifiedRsAddonsMblf); + _resetMetric(Glean.blocklist.mlbfSource); + _resetMetric(Glean.blocklist.mlbfGenerationTime); + _resetMetric(Glean.blocklist.mlbfStashTimeOldest); + _resetMetric(Glean.blocklist.mlbfStashTimeNewest); +} + +const MLBF_RECORD = { + id: "A blocklist entry that refers to a MLBF file", + // Higher than any last_modified in addons-bloomfilters.json: + last_modified: Date.now(), + attachment: { + size: 32, + hash: "6af648a5d6ce6dbee99b0aab1780d24d204977a6606ad670d5372ef22fac1052", + filename: "does-not-matter.bin", + }, + attachment_type: "bloomfilter-base", + generation_time: 1577833200000, +}; + +function enable_blocklist_v2_instead_of_useMLBF() { + Blocklist.allowDeprecatedBlocklistV2 = true; + Services.prefs.setBoolPref("extensions.blocklist.useMLBF", false); + // Sanity check: blocklist v2 has been enabled. + const { BlocklistPrivate } = ChromeUtils.importESModule( + "resource://gre/modules/Blocklist.sys.mjs" + ); + Assert.equal( + Blocklist.ExtensionBlocklist, + BlocklistPrivate.ExtensionBlocklistRS, + "ExtensionBlocklistRS should have been enabled" + ); +} + +async function load_mlbf_record_as_blob() { + const url = Services.io.newFileURI( + do_get_file("../data/mlbf-blocked1-unblocked2.bin") + ).spec; + Cu.importGlobalProperties(["fetch"]); + return (await fetch(url)).blob(); +} + +function getExtensionBlocklistMLBF() { + // ExtensionBlocklist.Blocklist is an ExtensionBlocklistMLBF if the useMLBF + // pref is set to true. + const { + BlocklistPrivate: { ExtensionBlocklistMLBF }, + } = ChromeUtils.importESModule("resource://gre/modules/Blocklist.sys.mjs"); + if (Blocklist.allowDeprecatedBlocklistV2) { + Assert.ok( + Services.prefs.getBoolPref("extensions.blocklist.useMLBF", false), + "blocklist.useMLBF should be true" + ); + } + return ExtensionBlocklistMLBF; +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js new file mode 100644 index 0000000000..c0221c446b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js @@ -0,0 +1,120 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Blocklist v3 will be enabled on release in bug 1824863. +// TODO bug 1824863: Remove this when blocklist v3 is enabled. +const IS_USING_BLOCKLIST_V3 = AppConstants.NIGHTLY_BUILD; + +// When bug 1639050 is fixed, this whole test can be removed as it is already +// covered by test_blocklist_mlbf_dump.js. + +// A known blocked version from bug 1626602. +// Same as in test_blocklist_mlbf_dump.js. +const blockedAddon = { + id: "{6f62927a-e380-401a-8c9e-c485b7d87f0d}", + version: "9.2.0", + signedDate: new Date(1588098908496), // 2020-04-28 (dummy date) + signedState: AddonManager.SIGNEDSTATE_SIGNED, +}; + +// A known blocked version from bug 1681884, blocklist v3 only but not v2, +// i.e. not listed in services/settings/dumps/blocklists/addons.json. +const blockedAddonV3only = { + id: "{011f65f0-7143-470a-83ca-20ec4297f3f4}", + version: "1.0", + // omiting signedDate/signedState: in blocklist v2 those don't matter. + // In v3 those do matter, so if blocklist v3 were to be enabled, then + // the test would fail. +}; + +// A known add-on that is not blocked, as of writing. It is likely not going +// to be blocked because it does not have any executable code. +// Same as in test_blocklist_mlbf_dump.js. +const nonBlockedAddon = { + id: "disable-ctrl-q-and-cmd-q@robwu.nl", + version: "1", + signedDate: new Date(1482430349000), // 2016-12-22 (actual signing time). + signedState: AddonManager.SIGNEDSTATE_SIGNED, +}; + +add_task( + { skip_if: () => IS_USING_BLOCKLIST_V3 }, + async function verify_blocklistv2_dump_first_run() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "A add-on that is known to be on the v2 blocklist should be blocked" + ); + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddonV3only), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "An add-on that is not part of the v2 blocklist should not be blocked" + ); + + Assert.equal( + await Blocklist.getAddonBlocklistState(nonBlockedAddon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "A known non-blocked add-on should not be blocked" + ); + } +); + +add_task( + { skip_if: () => !IS_USING_BLOCKLIST_V3 }, + async function verify_a_known_blocked_add_on_is_not_detected_as_blocked_at_first_run() { + const MLBF_LOAD_RESULTS = []; + const MLBF_LOAD_ATTEMPTS = []; + const onLoadAttempts = record => MLBF_LOAD_ATTEMPTS.push(record); + const onLoadResult = promise => MLBF_LOAD_RESULTS.push(promise); + spyOnExtensionBlocklistMLBF(onLoadAttempts, onLoadResult); + + // The addons blocklist data is not packaged and will be downloaded after install + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "A known blocked add-on should not be blocked at first" + ); + + await Assert.rejects( + MLBF_LOAD_RESULTS[0], + /DownloadError: Could not download addons-mlbf.bin/, + "Should not find any packaged attachment" + ); + + MLBF_LOAD_ATTEMPTS.length = 0; + MLBF_LOAD_RESULTS.length = 0; + + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Blocklist is still not populated" + ); + Assert.deepEqual( + MLBF_LOAD_ATTEMPTS, + [], + "MLBF is not fetched again after the first lookup" + ); + } +); + +function spyOnExtensionBlocklistMLBF(onLoadAttempts, onLoadResult) { + const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + // Tapping into the internals of ExtensionBlocklistMLBF._fetchMLBF to observe + const originalFetchMLBF = ExtensionBlocklistMLBF._fetchMLBF; + ExtensionBlocklistMLBF._fetchMLBF = async function (record) { + onLoadAttempts(record); + let promise = originalFetchMLBF.apply(this, arguments); + onLoadResult(promise); + return promise; + }; + + registerCleanupFunction( + () => (ExtensionBlocklistMLBF._fetchMLBF = originalFetchMLBF) + ); + + return ExtensionBlocklistMLBF; +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js new file mode 100644 index 0000000000..b11d1329cd --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// useMLBF=true case is covered by test_blocklist_mlbf.js +enable_blocklist_v2_instead_of_useMLBF(); + +const BLOCKLIST_DATA = [ + { + id: "foo", + guid: "myfoo", + versionRange: [ + { + severity: "3", + }, + ], + }, + { + blockID: "bar", + // we'll get a uuid as an `id` property from loadBlocklistRawData + guid: "mybar", + versionRange: [ + { + severity: "3", + }, + ], + }, +]; + +const BASE_BLOCKLIST_INFOURL = Services.prefs.getStringPref( + "extensions.blocklist.detailsURL" +); + +/* + * Check that add-on blocklist URLs are correctly exposed + * based on either blockID or id properties on the entries + * in remote settings. + */ +add_task(async function blocklistURL_check() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + await promiseStartupManager(); + await AddonTestUtils.loadBlocklistRawData({ extensions: BLOCKLIST_DATA }); + + let entry = await Blocklist.getAddonBlocklistEntry({ + id: "myfoo", + version: "1.0", + }); + Assert.equal(entry.url, BASE_BLOCKLIST_INFOURL + "foo.html"); + + entry = await Blocklist.getAddonBlocklistEntry({ + id: "mybar", + version: "1.0", + }); + Assert.equal(entry.url, BASE_BLOCKLIST_INFOURL + "bar.html"); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js new file mode 100644 index 0000000000..e8d03f088b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js @@ -0,0 +1,293 @@ +/* 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/. + */ + +// useMLBF=true does not offer special support for filtering by application ID. +// The same functionality is offered through filter_expression, which is tested +// by services/settings/test/unit/test_remote_settings_jexl_filters.js and +// test_blocklistchange.js. +enable_blocklist_v2_instead_of_useMLBF(); + +var ADDONS = [ + { + id: "test_bug449027_1@tests.mozilla.org", + name: "Bug 449027 Addon Test 1", + version: "5", + start: false, + appBlocks: false, + toolkitBlocks: false, + }, + { + id: "test_bug449027_2@tests.mozilla.org", + name: "Bug 449027 Addon Test 2", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_3@tests.mozilla.org", + name: "Bug 449027 Addon Test 3", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_4@tests.mozilla.org", + name: "Bug 449027 Addon Test 4", + version: "5", + start: false, + appBlocks: false, + toolkitBlocks: false, + }, + { + id: "test_bug449027_5@tests.mozilla.org", + name: "Bug 449027 Addon Test 5", + version: "5", + start: false, + appBlocks: false, + toolkitBlocks: false, + }, + { + id: "test_bug449027_6@tests.mozilla.org", + name: "Bug 449027 Addon Test 6", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_7@tests.mozilla.org", + name: "Bug 449027 Addon Test 7", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_8@tests.mozilla.org", + name: "Bug 449027 Addon Test 8", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_9@tests.mozilla.org", + name: "Bug 449027 Addon Test 9", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_10@tests.mozilla.org", + name: "Bug 449027 Addon Test 10", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_11@tests.mozilla.org", + name: "Bug 449027 Addon Test 11", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_12@tests.mozilla.org", + name: "Bug 449027 Addon Test 12", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_13@tests.mozilla.org", + name: "Bug 449027 Addon Test 13", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: false, + }, + { + id: "test_bug449027_14@tests.mozilla.org", + name: "Bug 449027 Addon Test 14", + version: "5", + start: false, + appBlocks: false, + toolkitBlocks: false, + }, + { + id: "test_bug449027_15@tests.mozilla.org", + name: "Bug 449027 Addon Test 15", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_16@tests.mozilla.org", + name: "Bug 449027 Addon Test 16", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_17@tests.mozilla.org", + name: "Bug 449027 Addon Test 17", + version: "5", + start: false, + appBlocks: false, + toolkitBlocks: false, + }, + { + id: "test_bug449027_18@tests.mozilla.org", + name: "Bug 449027 Addon Test 18", + version: "5", + start: false, + appBlocks: false, + toolkitBlocks: false, + }, + { + id: "test_bug449027_19@tests.mozilla.org", + name: "Bug 449027 Addon Test 19", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_20@tests.mozilla.org", + name: "Bug 449027 Addon Test 20", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_21@tests.mozilla.org", + name: "Bug 449027 Addon Test 21", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_22@tests.mozilla.org", + name: "Bug 449027 Addon Test 22", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_23@tests.mozilla.org", + name: "Bug 449027 Addon Test 23", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_24@tests.mozilla.org", + name: "Bug 449027 Addon Test 24", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, + { + id: "test_bug449027_25@tests.mozilla.org", + name: "Bug 449027 Addon Test 25", + version: "5", + start: false, + appBlocks: true, + toolkitBlocks: true, + }, +]; + +function createAddon(addon) { + return promiseInstallWebExtension({ + manifest: { + name: addon.name, + version: addon.version, + browser_specific_settings: { gecko: { id: addon.id } }, + }, + }); +} + +/** + * Checks that items are blocklisted correctly according to the current test. + * If a lastTest is provided checks that the notification dialog got passed + * the newly blocked items compared to the previous test. + */ +async function checkState(test, lastTest, callback) { + let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id)); + + const bls = Ci.nsIBlocklistService; + + await TestUtils.waitForCondition(() => + ADDONS.every( + (addon, i) => + addon[test] == (addons[i].blocklistState == bls.STATE_BLOCKED) + ) + ).catch(() => { + /* ignore exceptions; the following test will fail anyway. */ + }); + + for (let [i, addon] of ADDONS.entries()) { + var blocked = + addons[i].blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED; + equal( + blocked, + addon[test], + `Blocklist state should match expected for extension ${addon.id}, test ${test}` + ); + } +} + +add_task(async function test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + for (let addon of ADDONS) { + await createAddon(addon); + } + + let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id)); + for (var i = 0; i < ADDONS.length; i++) { + ok(addons[i], `Addon ${i + 1} should have been correctly installed`); + } + + await checkState("start"); +}); + +/** + * Load the toolkit based blocks + */ +add_task(async function test_pt2() { + await AddonTestUtils.loadBlocklistData( + do_get_file("../data/"), + "test_bug449027_toolkit" + ); + + await checkState("toolkitBlocks", "start"); +}); + +/** + * Load the application based blocks + */ +add_task(async function test_pt3() { + await AddonTestUtils.loadBlocklistData( + do_get_file("../data/"), + "test_bug449027_app" + ); + + await checkState("appBlocks", "toolkitBlocks"); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js new file mode 100644 index 0000000000..2ddb4fe514 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js @@ -0,0 +1,228 @@ +const { BlocklistPrivate } = ChromeUtils.importESModule( + "resource://gre/modules/Blocklist.sys.mjs" +); +const { Utils: RemoteSettingsUtils } = ChromeUtils.importESModule( + "resource://services-settings/Utils.sys.mjs" +); +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); + +const IS_ANDROID_WITH_BLOCKLIST_V2 = + AppConstants.platform == "android" && !AppConstants.NIGHTLY_BUILD; +let gBlocklistClients; + +async function clear_state() { + RemoteSettings.enablePreviewMode(undefined); + + for (let { client } of gBlocklistClients) { + // Remove last server times. + Services.prefs.clearUserPref(client.lastCheckTimePref); + + // Clear local DB. + await client.db.clear(); + } +} + +add_task(async function setup() { + AddonTestUtils.createAppInfo( + "XPCShell", + "xpcshell@tests.mozilla.org", + "1", + "" + ); + + // This will initialize the remote settings clients for blocklists. + BlocklistPrivate.ExtensionBlocklistRS.ensureInitialized(); + BlocklistPrivate.GfxBlocklistRS._ensureInitialized(); + + // ExtensionBlocklistMLBF is covered by test_blocklist_mlbf_dump.js. + gBlocklistClients = [ + { + client: BlocklistPrivate.ExtensionBlocklistRS._client, + expectHasDump: IS_ANDROID_WITH_BLOCKLIST_V2, + }, + { + client: BlocklistPrivate.GfxBlocklistRS._client, + expectHasDump: true, + }, + ]; + + await promiseStartupManager(); +}); + +add_task( + async function test_initial_dump_is_loaded_as_synced_when_collection_is_empty() { + for (let { client, expectHasDump } of gBlocklistClients) { + Assert.equal( + await RemoteSettingsUtils.hasLocalDump( + client.bucketName, + client.collectionName + ), + expectHasDump, + `Expected initial remote settings dump for ${client.collectionName}` + ); + } + } +); +add_task(clear_state); + +add_task(async function test_data_is_filtered_for_target() { + const initial = [ + { + guid: "foo", + matchName: "foo", + versionRange: [ + { + targetApplication: [], + maxVersion: "*", + minVersion: "0", + severity: "1", + }, + ], + }, + ]; + const noMatchingTarget = [ + { + guid: "foo", + matchName: "foo", + versionRange: [ + { + targetApplication: [{ guid: "Foo" }], + maxVersion: "*", + minVersion: "0", + severity: "3", + }, + ], + }, + { + guid: "foo", + matchName: "foo", + versionRange: [ + { + targetApplication: [{ guid: "XPCShell", maxVersion: "0.1" }], + maxVersion: "*", + minVersion: "0", + severity: "1", + }, + ], + }, + ]; + const oneMatch = [ + { + guid: "foo", + matchName: "foo", + versionRange: [ + { + targetApplication: [ + { + guid: "XPCShell", + }, + ], + }, + ], + }, + ]; + + const records = initial.concat(noMatchingTarget).concat(oneMatch); + + for (let { client } of gBlocklistClients) { + // Initialize the collection with some data + for (const record of records) { + await client.db.create(record); + } + + const internalData = await client.db.list(); + Assert.equal(internalData.length, records.length); + let filtered = await client.get({ syncIfEmpty: false }); + Assert.equal(filtered.length, 2); // only two matches. + } +}); +add_task(clear_state); + +add_task( + async function test_entries_are_filtered_when_jexl_filter_expression_is_present() { + const records = [ + { + guid: "foo", + matchName: "foo", + willMatch: true, + }, + { + guid: "foo", + matchName: "foo", + willMatch: true, + filter_expression: null, + }, + { + guid: "foo", + matchName: "foo", + willMatch: true, + filter_expression: "1 == 1", + }, + { + guid: "foo", + matchName: "foo", + willMatch: false, + filter_expression: "1 == 2", + }, + { + guid: "foo", + matchName: "foo", + willMatch: true, + filter_expression: "1 == 1", + versionRange: [ + { + targetApplication: [ + { + guid: "some-guid", + }, + ], + }, + ], + }, + { + guid: "foo", + matchName: "foo", + willMatch: false, // jexl prevails over versionRange. + filter_expression: "1 == 2", + versionRange: [ + { + targetApplication: [ + { + guid: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "*", + }, + ], + }, + ], + }, + ]; + for (let { client } of gBlocklistClients) { + for (const record of records) { + await client.db.create(record); + } + const list = await client.get({ + loadDumpIfNewer: false, + syncIfEmpty: false, + }); + equal(list.length, 4); + ok(list.every(e => e.willMatch)); + } + } +); +add_task(clear_state); + +add_task(async function test_bucketname_changes_when_preview_mode_is_enabled() { + for (const { client } of gBlocklistClients) { + equal(client.bucketName, "blocklists"); + } + + RemoteSettings.enablePreviewMode(true); + + for (const { client } of gBlocklistClients) { + equal(client.bucketName, "blocklists-preview", client.identifier); + } +}); +add_task(clear_state); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js new file mode 100644 index 0000000000..2b243ec650 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js @@ -0,0 +1,113 @@ +const EVENT_NAME = "blocklist-data-gfxItems"; + +const SAMPLE_GFX_RECORD = { + driverVersionComparator: "LESS_THAN_OR_EQUAL", + driverVersion: "8.17.12.5896", + vendor: "0x10de", + blockID: "g36", + feature: "DIRECT3D_9_LAYERS", + devices: ["0x0a6c", "geforce"], + featureStatus: "BLOCKED_DRIVER_VERSION", + last_modified: 9999999999999, // High timestamp to prevent load of dump + os: "WINNT 6.1", + id: "3f947f16-37c2-4e96-d356-78b26363729b", + versionRange: { minVersion: 0, maxVersion: "*" }, +}; + +add_task(async function test_sends_serialized_data() { + const expected = + "blockID:g36\tdevices:0x0a6c,geforce\tdriverVersion:8.17.12.5896\t" + + "driverVersionComparator:LESS_THAN_OR_EQUAL\tfeature:DIRECT3D_9_LAYERS\t" + + "featureStatus:BLOCKED_DRIVER_VERSION\tos:WINNT 6.1\tvendor:0x10de\t" + + "versionRange:0,*"; + let received; + const observe = (subject, topic, data) => { + received = data; + }; + Services.obs.addObserver(observe, EVENT_NAME); + await mockGfxBlocklistItems([SAMPLE_GFX_RECORD]); + Services.obs.removeObserver(observe, EVENT_NAME); + + equal(received, expected); +}); + +add_task(async function test_parsing_skips_devices_with_comma() { + let clonedItem = Cu.cloneInto(SAMPLE_GFX_RECORD, this); + clonedItem.devices[0] = "0x2,582"; + let rv = await mockGfxBlocklistItems([clonedItem]); + equal(rv[0].devices.length, 1); + equal(rv[0].devices[0], "geforce"); +}); + +add_task(async function test_empty_values_are_ignored() { + let received; + const observe = (subject, topic, data) => { + received = data; + }; + Services.obs.addObserver(observe, EVENT_NAME); + let clonedItem = Cu.cloneInto(SAMPLE_GFX_RECORD, this); + clonedItem.os = ""; + await mockGfxBlocklistItems([clonedItem]); + ok(!received.includes("os"), "Shouldn't send empty values"); + Services.obs.removeObserver(observe, EVENT_NAME); +}); + +add_task(async function test_empty_devices_are_ignored() { + let received; + const observe = (subject, topic, data) => { + received = data; + }; + Services.obs.addObserver(observe, EVENT_NAME); + let clonedItem = Cu.cloneInto(SAMPLE_GFX_RECORD, this); + clonedItem.devices = []; + await mockGfxBlocklistItems([clonedItem]); + ok(!received.includes("devices"), "Shouldn't send empty values"); + Services.obs.removeObserver(observe, EVENT_NAME); +}); + +add_task(async function test_version_range_default_values() { + const kTests = [ + { + input: { minVersion: "13.0b2", maxVersion: "42.0" }, + output: { minVersion: "13.0b2", maxVersion: "42.0" }, + }, + { + input: { maxVersion: "2.0" }, + output: { minVersion: "0", maxVersion: "2.0" }, + }, + { + input: { minVersion: "1.0" }, + output: { minVersion: "1.0", maxVersion: "*" }, + }, + { + input: { minVersion: " " }, + output: { minVersion: "0", maxVersion: "*" }, + }, + { + input: {}, + output: { minVersion: "0", maxVersion: "*" }, + }, + ]; + for (let test of kTests) { + let parsedEntries = await mockGfxBlocklistItems([ + { versionRange: test.input }, + ]); + equal(parsedEntries[0].versionRange.minVersion, test.output.minVersion); + equal(parsedEntries[0].versionRange.maxVersion, test.output.maxVersion); + } +}); + +add_task(async function test_blockid_attribute() { + const kTests = [ + { blockID: "g60", vendor: " 0x10de " }, + { feature: " DIRECT3D_9_LAYERS " }, + ]; + for (let test of kTests) { + let [rv] = await mockGfxBlocklistItems([test]); + if (test.blockID) { + equal(rv.blockID, test.blockID); + } else { + ok(!rv.hasOwnProperty("blockID"), "not expecting a blockID"); + } + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js new file mode 100644 index 0000000000..8f7ecbdf29 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests blocking of extensions by ID, name, creator, homepageURL, updateURL +// and RegExps for each. See bug 897735. + +// useMLBF=true only supports blocking by version+ID, not by other fields. +enable_blocklist_v2_instead_of_useMLBF(); + +const BLOCKLIST_DATA = { + extensions: [ + { + guid: null, + name: "/^Mozilla Corp\\.$/", + versionRange: [ + { + severity: "1", + targetApplication: [ + { + guid: "xpcshell@tests.mozilla.org", + maxVersion: "2.*", + minVersion: "1", + }, + ], + }, + ], + }, + { + guid: "/block2/", + name: "/^Moz/", + homepageURL: "/\\.dangerous\\.com/", + updateURL: "/\\.dangerous\\.com/", + versionRange: [ + { + severity: "3", + targetApplication: [ + { + guid: "xpcshell@tests.mozilla.org", + maxVersion: "2.*", + minVersion: "1", + }, + ], + }, + ], + }, + ], +}; + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + + await promiseStartupManager(); + + // Should get blocked by name + await promiseInstallWebExtension({ + manifest: { + name: "Mozilla Corp.", + version: "1.0", + browser_specific_settings: { gecko: { id: "block1@tests.mozilla.org" } }, + }, + }); + + // Should get blocked by all the attributes. + await promiseInstallWebExtension({ + manifest: { + name: "Moz-addon", + version: "1.0", + homepage_url: "https://www.extension.dangerous.com/", + browser_specific_settings: { + gecko: { + id: "block2@tests.mozilla.org", + update_url: "https://www.extension.dangerous.com/update.json", + }, + }, + }, + }); + + // Fails to get blocked because of a different ID even though other + // attributes match against a blocklist entry. + await promiseInstallWebExtension({ + manifest: { + name: "Moz-addon", + version: "1.0", + homepage_url: "https://www.extension.dangerous.com/", + browser_specific_settings: { + gecko: { + id: "block3@tests.mozilla.org", + update_url: "https://www.extension.dangerous.com/update.json", + }, + }, + }, + }); + + let [a1, a2, a3] = await AddonManager.getAddonsByIDs([ + "block1@tests.mozilla.org", + "block2@tests.mozilla.org", + "block3@tests.mozilla.org", + ]); + Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + Assert.equal(a3.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); +}); + +add_task(async function test_blocks() { + await AddonTestUtils.loadBlocklistRawData(BLOCKLIST_DATA); + + let [a1, a2, a3] = await AddonManager.getAddonsByIDs([ + "block1@tests.mozilla.org", + "block2@tests.mozilla.org", + "block3@tests.mozilla.org", + ]); + Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED); + Assert.equal(a3.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js new file mode 100644 index 0000000000..1f36fc046d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js @@ -0,0 +1,267 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); +AddonTestUtils.useRealCertChecks = true; + +// A real, signed XPI for use in the test. +const SIGNED_ADDON_XPI_FILE = do_get_file("../data/webext-implicit-id.xpi"); +const SIGNED_ADDON_ID = "webext_implicit_id@tests.mozilla.org"; +const SIGNED_ADDON_VERSION = "1.0"; +const SIGNED_ADDON_KEY = `${SIGNED_ADDON_ID}:${SIGNED_ADDON_VERSION}`; +const SIGNED_ADDON_SIGN_TIME = 1459980789000; // notBefore of certificate. + +// A real, signed sitepermission XPI for use in the test. +const SIGNED_SITEPERM_XPI_FILE = do_get_file("webmidi_permission.xpi"); +const SIGNED_SITEPERM_ADDON_ID = "webmidi@test.mozilla.org"; +const SIGNED_SITEPERM_ADDON_VERSION = "1.0.2"; +const SIGNED_SITEPERM_KEY = `${SIGNED_SITEPERM_ADDON_ID}:${SIGNED_SITEPERM_ADDON_VERSION}`; +const SIGNED_SITEPERM_SIGN_TIME = 1637606460000; // notBefore of certificate. + +function mockMLBF({ blocked = [], notblocked = [], generationTime }) { + // Mock _fetchMLBF to be able to have a deterministic cascade filter. + ExtensionBlocklistMLBF._fetchMLBF = async () => { + return { + cascadeFilter: { + has(blockKey) { + if (blocked.includes(blockKey)) { + return true; + } + if (notblocked.includes(blockKey)) { + return false; + } + throw new Error(`Block entry must explicitly be listed: ${blockKey}`); + }, + }, + generationTime, + }; + }; +} + +add_task(async function setup() { + await promiseStartupManager(); + mockMLBF({}); + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [MLBF_RECORD], + }); +}); + +// Checks: Initially unblocked, then blocked, then unblocked again. +add_task(async function signed_xpi_initially_unblocked() { + mockMLBF({ + blocked: [], + notblocked: [SIGNED_ADDON_KEY], + generationTime: SIGNED_ADDON_SIGN_TIME + 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + + await promiseInstallFile(SIGNED_ADDON_XPI_FILE); + + let addon = await promiseAddonByID(SIGNED_ADDON_ID); + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + mockMLBF({ + blocked: [SIGNED_ADDON_KEY], + notblocked: [], + generationTime: SIGNED_ADDON_SIGN_TIME + 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED); + Assert.deepEqual( + await Blocklist.getAddonBlocklistEntry(addon), + { + state: Ci.nsIBlocklistService.STATE_BLOCKED, + url: "https://addons.mozilla.org/en-US/xpcshell/blocked-addon/webext_implicit_id@tests.mozilla.org/1.0/", + }, + "Blocked addon should have blocked entry" + ); + + mockMLBF({ + blocked: [SIGNED_ADDON_KEY], + notblocked: [], + // MLBF generationTime is older, so "blocked" entry should not apply. + generationTime: SIGNED_ADDON_SIGN_TIME - 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await addon.uninstall(); +}); + +// Checks: Initially blocked on install, then unblocked. +add_task(async function signed_xpi_blocked_on_install() { + mockMLBF({ + blocked: [SIGNED_ADDON_KEY], + notblocked: [], + generationTime: SIGNED_ADDON_SIGN_TIME + 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + + await promiseInstallFile(SIGNED_ADDON_XPI_FILE); + let addon = await promiseAddonByID(SIGNED_ADDON_ID); + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED); + Assert.ok(addon.appDisabled, "Blocked add-on is disabled on install"); + + mockMLBF({ + blocked: [], + notblocked: [SIGNED_ADDON_KEY], + generationTime: SIGNED_ADDON_SIGN_TIME - 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + Assert.ok(!addon.appDisabled, "Re-enabled after unblock"); + + await addon.uninstall(); +}); + +// An unsigned add-on cannot be blocked. +add_task(async function unsigned_not_blocked() { + const UNSIGNED_ADDON_ID = "not-signed@tests.mozilla.org"; + const UNSIGNED_ADDON_VERSION = "1.0"; + const UNSIGNED_ADDON_KEY = `${UNSIGNED_ADDON_ID}:${UNSIGNED_ADDON_VERSION}`; + mockMLBF({ + blocked: [UNSIGNED_ADDON_KEY], + notblocked: [], + generationTime: SIGNED_ADDON_SIGN_TIME + 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + + let unsignedAddonFile = createTempWebExtensionFile({ + manifest: { + version: UNSIGNED_ADDON_VERSION, + browser_specific_settings: { gecko: { id: UNSIGNED_ADDON_ID } }, + }, + }); + + // Unsigned add-ons can generally only be loaded as a temporary install. + let [addon] = await Promise.all([ + AddonManager.installTemporaryAddon(unsignedAddonFile), + promiseWebExtensionStartup(UNSIGNED_ADDON_ID), + ]); + Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_MISSING); + Assert.equal(addon.signedDate, null); + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + Assert.equal( + await Blocklist.getAddonBlocklistState(addon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Unsigned temporary add-on is not blocked" + ); + await addon.uninstall(); +}); + +// To make sure that unsigned_not_blocked did not trivially pass, we also check +// that add-ons can actually be blocked when installed as a temporary add-on. +add_task(async function signed_temporary() { + mockMLBF({ + blocked: [SIGNED_ADDON_KEY], + notblocked: [], + generationTime: SIGNED_ADDON_SIGN_TIME + 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + + await Assert.rejects( + AddonManager.installTemporaryAddon(SIGNED_ADDON_XPI_FILE), + /Add-on webext_implicit_id@tests.mozilla.org is not compatible with application version/, + "Blocklisted add-on cannot be installed" + ); +}); + +// A privileged add-on cannot be blocked by the MLBF. +// It can still be blocked by a stash, which is tested in +// privileged_addon_blocked_by_stash in test_blocklist_mlbf_stashes.js. +add_task(async function privileged_xpi_not_blocked() { + mockMLBF({ + blocked: ["test@tests.mozilla.org:2.0"], + notblocked: [], + generationTime: 1546297200000, // 1 jan 2019 = after the cert's notBefore + }); + await ExtensionBlocklistMLBF._onUpdate(); + + await promiseInstallFile( + do_get_file("../data/signing_checks/privileged.xpi") + ); + let addon = await promiseAddonByID("test@tests.mozilla.org"); + Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_PRIVILEGED); + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + await addon.uninstall(); +}); + +// Langpacks cannot be blocked via the MLBF on Nightly. +// It can still be blocked by a stash, which is tested in +// langpack_blocked_by_stash in test_blocklist_mlbf_stashes.js. +add_task(async function langpack_not_blocked_on_Nightly() { + mockMLBF({ + blocked: ["langpack-klingon@firefox.mozilla.org:1.0"], + notblocked: [], + generationTime: 1546297200000, // 1 jan 2019 = after the cert's notBefore + }); + await ExtensionBlocklistMLBF._onUpdate(); + + await promiseInstallFile( + do_get_file("../data/signing_checks/langpack_signed.xpi") + ); + let addon = await promiseAddonByID("langpack-klingon@firefox.mozilla.org"); + Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED); + if (AppConstants.NIGHTLY_BUILD) { + // Langpacks built for Nightly are currently signed by releng and not + // submitted to AMO, so we have to ignore the blocks of the MLBF. + Assert.equal( + addon.blocklistState, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Langpacks cannot be blocked via the MLBF" + ); + } else { + // On non-Nightly, langpacks are submitted through AMO so we will enforce + // the MLBF blocklist for them. + Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED); + } + await addon.uninstall(); +}); + +// Checks: Signed sitepermission addon, initially blocked on install, then unblocked. +add_task(async function signed_sitepermission_xpi_blocked_on_install() { + mockMLBF({ + blocked: [SIGNED_SITEPERM_KEY], + notblocked: [], + generationTime: SIGNED_SITEPERM_SIGN_TIME + 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + + await promiseInstallFile(SIGNED_SITEPERM_XPI_FILE); + let addon = await promiseAddonByID(SIGNED_SITEPERM_ADDON_ID); + // NOTE: if this assertion fails, then SIGNED_SITEPERM_SIGN_TIME has to be + // updated accordingly otherwise the addon would not be blocked on install + // as this test expects (using the value got from `addon.signedDate.getTime()`) + equal( + addon.signedDate?.getTime(), + SIGNED_SITEPERM_SIGN_TIME, + "The addon xpi has the expected signedDate timestamp" + ); + Assert.equal( + addon.blocklistState, + Ci.nsIBlocklistService.STATE_BLOCKED, + "Got the expected STATE_BLOCKED blocklistState" + ); + Assert.ok(addon.appDisabled, "Blocked add-on is disabled on install"); + + mockMLBF({ + blocked: [], + notblocked: [SIGNED_SITEPERM_KEY], + generationTime: SIGNED_SITEPERM_SIGN_TIME - 1, + }); + await ExtensionBlocklistMLBF._onUpdate(); + Assert.equal( + addon.blocklistState, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Got the expected STATE_NOT_BLOCKED blocklistState" + ); + Assert.ok(!addon.appDisabled, "Re-enabled after unblock"); + + await addon.uninstall(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js new file mode 100644 index 0000000000..3cbd607339 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js @@ -0,0 +1,156 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * @fileOverview Verifies that the MLBF dump of the addons blocklist is + * correctly registered. + */ + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + +// A known blocked version from bug 1626602. +const blockedAddon = { + id: "{6f62927a-e380-401a-8c9e-c485b7d87f0d}", + version: "9.2.0", + // The following date is the date of the first checked in MLBF. Any MLBF + // generated in the future should be generated after this date, to be useful. + signedDate: new Date(1588098908496), // 2020-04-28 (dummy date) + signedState: AddonManager.SIGNEDSTATE_SIGNED, +}; + +// A known add-on that is not blocked, as of writing. It is likely not going +// to be blocked because it does not have any executable code. +const nonBlockedAddon = { + id: "disable-ctrl-q-and-cmd-q@robwu.nl", + version: "1", + signedDate: new Date(1482430349000), // 2016-12-22 (actual signing time). + signedState: AddonManager.SIGNEDSTATE_SIGNED, +}; + +async function sha256(arrayBuffer) { + Cu.importGlobalProperties(["crypto"]); + let hash = await crypto.subtle.digest("SHA-256", arrayBuffer); + const toHex = b => b.toString(16).padStart(2, "0"); + return Array.from(new Uint8Array(hash), toHex).join(""); +} + +// A list of { inputRecord, downloadPromise }: +// - inputRecord is the record that was used for looking up the MLBF. +// - downloadPromise is the result of trying to download it. +const observed = []; + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + ExtensionBlocklistMLBF.ensureInitialized(); + + // Tapping into the internals of ExtensionBlocklistMLBF._fetchMLBF to observe + // MLBF request details. + + // Despite being called "download", this does not actually access the network + // when there is a valid dump. + const originalImpl = ExtensionBlocklistMLBF._client.attachments.download; + ExtensionBlocklistMLBF._client.attachments.download = function (record) { + let downloadPromise = originalImpl.apply(this, arguments); + observed.push({ inputRecord: record, downloadPromise }); + return downloadPromise; + }; + + await promiseStartupManager(); +}); + +async function verifyBlocklistWorksWithDump() { + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "A add-on that is known to be on the blocklist should be blocked" + ); + Assert.equal( + await Blocklist.getAddonBlocklistState(nonBlockedAddon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "A known non-blocked add-on should not be blocked" + ); +} + +add_task(async function verify_dump_first_run() { + await verifyBlocklistWorksWithDump(); + Assert.equal(observed.length, 1, "expected number of MLBF download requests"); + + const { inputRecord, downloadPromise } = observed.pop(); + + Assert.ok(inputRecord, "addons-bloomfilters collection dump exists"); + + const downloadResult = await downloadPromise; + + // Verify that the "download" result really originates from the local dump. + // "dump_match" means that the record exists in the collection and that an + // attachment was found. + // + // If this fails: + // - "dump_fallback" means that the MLBF attachment is out of sync with the + // collection data. + // - undefined could mean that the implementation of Attachments.sys.mjs changed. + Assert.equal( + downloadResult._source, + "dump_match", + "MLBF attachment should match the RemoteSettings collection" + ); + + Assert.equal( + await sha256(downloadResult.buffer), + inputRecord.attachment.hash, + "The content of the attachment should actually matches the record" + ); +}); + +add_task(async function use_dump_fallback_when_collection_is_out_of_sync() { + await AddonTestUtils.loadBlocklistRawData({ + // last_modified higher than any value in addons-bloomfilters.json. + extensionsMLBF: [{ last_modified: Date.now() }], + }); + Assert.equal(observed.length, 1, "Expected new download on update"); + + const { inputRecord, downloadPromise } = observed.pop(); + Assert.equal(inputRecord, null, "No MLBF record found"); + + const downloadResult = await downloadPromise; + Assert.equal( + downloadResult._source, + "dump_fallback", + "should have used fallback despite the absence of a MLBF record" + ); + + await verifyBlocklistWorksWithDump(); + Assert.equal(observed.length, 0, "Blocklist uses cached result"); +}); + +// Verifies that the dump would supersede local data. This can happen after an +// application upgrade, where the local database contains outdated records from +// a previous application version. +add_task(async function verify_dump_supersedes_old_dump() { + // Delete in-memory value; otherwise the cached record from the previous test + // task would be re-used and nothing would be downloaded. + delete ExtensionBlocklistMLBF._mlbfData; + + await AddonTestUtils.loadBlocklistRawData({ + // last_modified lower than any value in addons-bloomfilters.json. + extensionsMLBF: [{ last_modified: 1 }], + }); + Assert.equal(observed.length, 1, "Expected new download on update"); + + const { inputRecord, downloadPromise } = observed.pop(); + Assert.ok(inputRecord, "should have read from addons-bloomfilters dump"); + + const downloadResult = await downloadPromise; + Assert.equal( + downloadResult._source, + "dump_match", + "Should have replaced outdated collection records with dump" + ); + + await verifyBlocklistWorksWithDump(); + Assert.equal(observed.length, 0, "Blocklist uses cached result"); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js new file mode 100644 index 0000000000..92bf61dbde --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js @@ -0,0 +1,231 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * @fileOverview Tests the MLBF and RemoteSettings synchronization logic. + */ + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + +const { Downloader } = ChromeUtils.importESModule( + "resource://services-settings/Attachments.sys.mjs" +); + +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + +// This test needs to interact with the RemoteSettings client. +ExtensionBlocklistMLBF.ensureInitialized(); + +add_task(async function fetch_invalid_mlbf_record() { + let invalidRecord = { + attachment: { size: 1, hash: "definitely not valid" }, + generation_time: 1, + }; + + // _fetchMLBF(invalidRecord) may succeed if there is a MLBF dump packaged with + // the application. This test intentionally hides the actual path to get + // deterministic results. To check whether the dump is correctly registered, + // run test_blocklist_mlbf_dump.js + + // Forget about the packaged attachment. + Downloader._RESOURCE_BASE_URL = "invalid://bogus"; + // NetworkError is expected here. The JSON.parse error could be triggered via + // _baseAttachmentsURL < downloadAsBytes < download < download < _fetchMLBF if + // the request to services.settings.server ("data:,#remote-settings-dummy/v1") + // is fulfilled (but with invalid JSON). That request is not expected to be + // fulfilled in the first place, but that is not a concern of this test. + // This test passes if _fetchMLBF() rejects when given an invalid record. + await Assert.rejects( + ExtensionBlocklistMLBF._fetchMLBF(invalidRecord), + /NetworkError|SyntaxError: JSON\.parse/, + "record not found when there is no packaged MLBF" + ); +}); + +// Other tests can mock _testMLBF, so let's verify that it works as expected. +add_task(async function fetch_valid_mlbf() { + await ExtensionBlocklistMLBF._client.db.saveAttachment( + ExtensionBlocklistMLBF.RS_ATTACHMENT_ID, + { record: MLBF_RECORD, blob: await load_mlbf_record_as_blob() } + ); + + const result = await ExtensionBlocklistMLBF._fetchMLBF(MLBF_RECORD); + Assert.equal(result.cascadeHash, MLBF_RECORD.attachment.hash, "hash OK"); + Assert.equal(result.generationTime, MLBF_RECORD.generation_time, "time OK"); + Assert.ok(result.cascadeFilter.has("@blocked:1"), "item blocked"); + Assert.ok(!result.cascadeFilter.has("@unblocked:2"), "item not blocked"); + + const result2 = await ExtensionBlocklistMLBF._fetchMLBF({ + attachment: { size: 1, hash: "invalid" }, + generation_time: Date.now(), + }); + Assert.equal( + result2.cascadeHash, + MLBF_RECORD.attachment.hash, + "The cached MLBF should be used when the attachment is invalid" + ); + + // The attachment is kept in the database for use by the next test task. +}); + +// Test that results of the public API are consistent with the MLBF file. +add_task(async function public_api_uses_mlbf() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + await promiseStartupManager(); + + const blockedAddon = { + id: "@blocked", + version: "1", + signedDate: new Date(0), // a date in the past, before MLBF's generationTime. + signedState: AddonManager.SIGNEDSTATE_SIGNED, + }; + const nonBlockedAddon = { + id: "@unblocked", + version: "2", + signedDate: new Date(0), // a date in the past, before MLBF's generationTime. + signedState: AddonManager.SIGNEDSTATE_SIGNED, + }; + + await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [MLBF_RECORD] }); + + Assert.deepEqual( + await Blocklist.getAddonBlocklistEntry(blockedAddon), + { + state: Ci.nsIBlocklistService.STATE_BLOCKED, + url: "https://addons.mozilla.org/en-US/xpcshell/blocked-addon/@blocked/1/", + }, + "Blocked addon should have blocked entry" + ); + + Assert.deepEqual( + await Blocklist.getAddonBlocklistEntry(nonBlockedAddon), + null, + "Non-blocked addon should not be blocked" + ); + + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "Blocked entry should have blocked state" + ); + + Assert.equal( + await Blocklist.getAddonBlocklistState(nonBlockedAddon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Non-blocked entry should have unblocked state" + ); + + // Note: Blocklist collection and attachment carries over to the next test. +}); + +// Verifies that the metadata (time of validity) of an updated MLBF record is +// correctly used, even if the MLBF itself has not changed. +add_task(async function fetch_updated_mlbf_same_hash() { + const recordUpdate = { + ...MLBF_RECORD, + generation_time: MLBF_RECORD.generation_time + 1, + }; + const blockedAddonUpdate = { + id: "@blocked", + version: "1", + signedDate: new Date(recordUpdate.generation_time), + signedState: AddonManager.SIGNEDSTATE_SIGNED, + }; + + // The blocklist already includes "@blocked:1", but the last specified + // generation time is MLBF_RECORD.generation_time. So the addon cannot be + // blocked, because the block decision could be a false positive. + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddonUpdate), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Add-on not blocked before blocklist update" + ); + + await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [recordUpdate] }); + // The MLBF is now known to apply to |blockedAddonUpdate|. + + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddonUpdate), + Ci.nsIBlocklistService.STATE_BLOCKED, + "Add-on blocked after update" + ); + + // Note: Blocklist collection and attachment carries over to the next test. +}); + +// Checks the remaining cases of database corruption that haven't been handled +// before. +add_task(async function handle_database_corruption() { + const blockedAddon = { + id: "@blocked", + version: "1", + signedDate: new Date(0), // a date in the past, before MLBF's generationTime. + signedState: AddonManager.SIGNEDSTATE_SIGNED, + }; + async function checkBlocklistWorks() { + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "Add-on should be blocked by the blocklist" + ); + } + + let fetchCount = 0; + const originalFetchMLBF = ExtensionBlocklistMLBF._fetchMLBF; + ExtensionBlocklistMLBF._fetchMLBF = function () { + ++fetchCount; + return originalFetchMLBF.apply(this, arguments); + }; + + // In the fetch_invalid_mlbf_record we checked that a cached / packaged MLBF + // attachment is used as a fallback when the record is invalid. Here we also + // check that there is a fallback when there is no record at all. + + // Include a dummy record in the list, to prevent RemoteSettings from + // importing a JSON dump with unexpected records. + await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [{}] }); + Assert.equal(fetchCount, 1, "MLBF read once despite bad record"); + // When the collection is empty, the last known MLBF should be used anyway. + await checkBlocklistWorks(); + Assert.equal(fetchCount, 1, "MLBF not read again by blocklist query"); + + // Now we also remove the cached file... + await ExtensionBlocklistMLBF._client.db.saveAttachment( + ExtensionBlocklistMLBF.RS_ATTACHMENT_ID, + null + ); + Assert.equal(fetchCount, 1, "MLBF not read again after attachment deletion"); + // Deleting the file shouldn't cause issues because the MLBF is loaded once + // and then kept in memory. + await checkBlocklistWorks(); + Assert.equal(fetchCount, 1, "MLBF not read again by blocklist query 2"); + + // Force an update while we don't have any blocklist data nor cache. + await ExtensionBlocklistMLBF._onUpdate(); + Assert.equal(fetchCount, 2, "MLBF read again at forced update"); + // As a fallback, continue to use the in-memory version of the blocklist. + await checkBlocklistWorks(); + Assert.equal(fetchCount, 2, "MLBF not read again by blocklist query 3"); + + // Memory gone, e.g. after a browser restart. + delete ExtensionBlocklistMLBF._mlbfData; + delete ExtensionBlocklistMLBF._stashes; + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Blocklist can't work if all blocklist data is gone" + ); + Assert.equal(fetchCount, 3, "MLBF read again after restart/cleared cache"); + Assert.equal( + await Blocklist.getAddonBlocklistState(blockedAddon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Blocklist can still not work if all blocklist data is gone" + ); + // Ideally, the client packages a dump. But if the client did not package the + // dump, then it should not be trying to read the data over and over again. + Assert.equal(fetchCount, 3, "MLBF not read again despite absence of MLBF"); + + ExtensionBlocklistMLBF._fetchMLBF = originalFetchMLBF; +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js new file mode 100644 index 0000000000..e129efc793 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js @@ -0,0 +1,219 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); +const MLBF_LOAD_ATTEMPTS = []; +ExtensionBlocklistMLBF._fetchMLBF = async record => { + MLBF_LOAD_ATTEMPTS.push(record); + return { + generationTime: 0, + cascadeFilter: { + has(blockKey) { + if (blockKey === "@onlyblockedbymlbf:1") { + return true; + } + throw new Error("bloom filter should not be used in this test"); + }, + }, + }; +}; + +async function checkBlockState(addonId, version, expectBlocked) { + let addon = { + id: addonId, + version, + // Note: signedDate is missing, so the MLBF does not apply + // and we will effectively only test stashing. + }; + let state = await Blocklist.getAddonBlocklistState(addon); + if (expectBlocked) { + Assert.equal(state, Ci.nsIBlocklistService.STATE_BLOCKED); + } else { + Assert.equal(state, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + } +} + +add_task(async function setup() { + await promiseStartupManager(); +}); + +// Tests that add-ons can be blocked / unblocked via the stash. +add_task(async function basic_stash() { + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [ + { + stash_time: 0, + stash: { + blocked: ["@blocked:1"], + unblocked: ["@notblocked:2"], + }, + }, + ], + }); + await checkBlockState("@blocked", "1", true); + await checkBlockState("@notblocked", "2", false); + // Not in stash (but unsigned, so shouldn't reach MLBF): + await checkBlockState("@blocked", "2", false); + + Assert.equal( + await Blocklist.getAddonBlocklistState({ + id: "@onlyblockedbymlbf", + version: "1", + signedDate: new Date(0), // = the MLBF's generationTime. + signedState: AddonManager.SIGNEDSTATE_SIGNED, + }), + Ci.nsIBlocklistService.STATE_BLOCKED, + "falls through to MLBF if entry is not found in stash" + ); + + Assert.deepEqual(MLBF_LOAD_ATTEMPTS, [null], "MLBF attachment not found"); +}); + +// To complement the privileged_xpi_not_blocked in test_blocklist_mlbf.js, +// verify that privileged add-ons can still be blocked through stashes. +add_task(async function privileged_addon_blocked_by_stash() { + const system_addon = { + id: "@blocked", + version: "1", + signedDate: new Date(0), // = the MLBF's generationTime. + signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, + }; + Assert.equal( + await Blocklist.getAddonBlocklistState(system_addon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "Privileged add-ons can still be blocked by a stash" + ); + + system_addon.signedState = AddonManager.SIGNEDSTATE_SYSTEM; + Assert.equal( + await Blocklist.getAddonBlocklistState(system_addon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "Privileged system add-ons can still be blocked by a stash" + ); + + // For comparison, when an add-on is only blocked by a MLBF, the block + // decision is ignored. + system_addon.id = "@onlyblockedbymlbf"; + Assert.equal( + await Blocklist.getAddonBlocklistState(system_addon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Privileged add-ons cannot be blocked via a MLBF" + ); + // (note that we haven't checked that SIGNEDSTATE_PRIVILEGED is not blocked + // via the MLBF, but that is already covered by test_blocklist_mlbf.js ). +}); + +// To complement langpack_not_blocked_on_Nightly in test_blocklist_mlbf.js, +// verify that langpacks can still be blocked through stashes. +add_task(async function langpack_blocked_by_stash() { + const langpack_addon = { + id: "@blocked", + type: "locale", + version: "1", + signedDate: new Date(0), // = the MLBF's generationTime. + signedState: AddonManager.SIGNEDSTATE_SIGNED, + }; + Assert.equal( + await Blocklist.getAddonBlocklistState(langpack_addon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "Langpack add-ons can still be blocked by a stash" + ); + + // For comparison, when an add-on is only blocked by a MLBF, the block + // decision is ignored on Nightly (but blocked on non-Nightly). + langpack_addon.id = "@onlyblockedbymlbf"; + if (AppConstants.NIGHTLY_BUILD) { + Assert.equal( + await Blocklist.getAddonBlocklistState(langpack_addon), + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + "Langpack add-ons cannot be blocked via a MLBF on Nightly" + ); + } else { + Assert.equal( + await Blocklist.getAddonBlocklistState(langpack_addon), + Ci.nsIBlocklistService.STATE_BLOCKED, + "Langpack add-ons can be blocked via a MLBF on non-Nightly" + ); + } +}); + +// Tests that invalid stash entries are ignored. +add_task(async function invalid_stashes() { + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [ + {}, + { stash: null }, + { stash: 1 }, + { stash: {} }, + { stash: { blocked: ["@broken:1", "@okid:1"] } }, + { stash: { unblocked: ["@broken:2"] } }, + // The only correct entry: + { stash: { blocked: ["@okid:2"], unblocked: ["@okid:1"] } }, + { stash: { blocked: ["@broken:1", "@okid:1"] } }, + { stash: { unblocked: ["@broken:2", "@okid:2"] } }, + ], + }); + // The valid stash entry should be applied: + await checkBlockState("@okid", "1", false); + await checkBlockState("@okid", "2", true); + // Entries from invalid stashes should be ignored: + await checkBlockState("@broken", "1", false); + await checkBlockState("@broken", "2", false); +}); + +// Blocklist stashes should be processed in the reverse chronological order. +add_task(async function stash_time_order() { + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [ + // "@a:1" and "@a:2" are blocked at time 1, but unblocked later. + { stash_time: 2, stash: { blocked: [], unblocked: ["@a:1"] } }, + { stash_time: 1, stash: { blocked: ["@a:1", "@a:2"], unblocked: [] } }, + { stash_time: 3, stash: { blocked: [], unblocked: ["@a:2"] } }, + + // "@b:1" and "@b:2" are unblocked at time 4, but blocked later. + { stash_time: 5, stash: { blocked: ["@b:1"], unblocked: [] } }, + { stash_time: 4, stash: { blocked: [], unblocked: ["@b:1", "@b:2"] } }, + { stash_time: 6, stash: { blocked: ["@b:2"], unblocked: [] } }, + ], + }); + await checkBlockState("@a", "1", false); + await checkBlockState("@a", "2", false); + + await checkBlockState("@b", "1", true); + await checkBlockState("@b", "2", true); +}); + +// Attachments with unsupported attachment_type should be ignored. +add_task(async function mlbf_bloomfilter_full_ignored() { + MLBF_LOAD_ATTEMPTS.length = 0; + + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [{ attachment_type: "bloomfilter-full", attachment: {} }], + }); + + // Only bloomfilter-base records should be used. + // Since there are no such records, we shouldn't find anything. + Assert.deepEqual(MLBF_LOAD_ATTEMPTS, [null], "no matching MLBFs found"); +}); + +// Tests that the most recent MLBF is downloaded. +add_task(async function mlbf_generation_time_recent() { + MLBF_LOAD_ATTEMPTS.length = 0; + const records = [ + { attachment_type: "bloomfilter-base", attachment: {}, generation_time: 2 }, + { attachment_type: "bloomfilter-base", attachment: {}, generation_time: 3 }, + { attachment_type: "bloomfilter-base", attachment: {}, generation_time: 1 }, + ]; + await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: records }); + Assert.equal( + MLBF_LOAD_ATTEMPTS[0].generation_time, + 3, + "expected to load most recent MLBF" + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js new file mode 100644 index 0000000000..6ad8e9c2ac --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js @@ -0,0 +1,188 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + +const { Downloader } = ChromeUtils.importESModule( + "resource://services-settings/Attachments.sys.mjs" +); + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const OLDEST_STASH = { stash: { blocked: [], unblocked: [] }, stash_time: 2e6 }; +const NEWEST_STASH = { stash: { blocked: [], unblocked: [] }, stash_time: 5e6 }; +const RECORDS_WITH_STASHES_AND_MLBF = [MLBF_RECORD, OLDEST_STASH, NEWEST_STASH]; + +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + +function assertTelemetryScalars(expectedScalars) { + // On Android, we only report to the Glean system telemetry system. + if (IS_ANDROID_BUILD) { + info( + `Skip assertions on collected samples for ${expectedScalars} on android builds` + ); + return; + } + let scalars = TelemetryTestUtils.getProcessScalars("parent"); + for (const scalarName of Object.keys(expectedScalars || {})) { + equal( + scalars[scalarName], + expectedScalars[scalarName], + `Got the expected value for ${scalarName} scalar` + ); + } +} + +function toUTC(time) { + return new Date(time).toUTCString(); +} + +add_task(async function setup() { + if (!IS_ANDROID_BUILD) { + // FOG needs a profile directory to put its data in. + do_get_profile(); + // FOG needs to be initialized in order for data to flow. + Services.fog.initializeFOG(); + } + await TelemetryController.testSetup(); + await promiseStartupManager(); + + // Disable the packaged record and attachment to make sure that the test + // will not fall back to the packaged attachments. + Downloader._RESOURCE_BASE_URL = "invalid://bogus"; +}); + +add_task(async function test_initialization() { + resetBlocklistTelemetry(); + ExtensionBlocklistMLBF.ensureInitialized(); + + Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfSource)); + Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfGenerationTime)); + Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfStashTimeOldest)); + Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfStashTimeNewest)); + + assertTelemetryScalars({ + // In other parts of this test, this value is not checked any more. + // test_blocklist_telemetry.js already checks lastModified_rs_addons_mlbf. + "blocklist.lastModified_rs_addons_mlbf": undefined, + "blocklist.mlbf_source": undefined, + "blocklist.mlbf_generation_time": undefined, + "blocklist.mlbf_stash_time_oldest": undefined, + "blocklist.mlbf_stash_time_newest": undefined, + }); +}); + +// Test what happens if there is no blocklist data at all. +add_task(async function test_without_mlbf() { + resetBlocklistTelemetry(); + // Add one (invalid) value to the blocklist, to prevent the RemoteSettings + // client from importing the JSON dump (which could potentially cause the + // test to fail due to the unexpected imported records). + await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [{}] }); + Assert.equal("unknown", testGetValue(Glean.blocklist.mlbfSource)); + + Assert.equal(0, testGetValue(Glean.blocklist.mlbfGenerationTime).getTime()); + Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime()); + Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime()); + + assertTelemetryScalars({ + "blocklist.mlbf_source": "unknown", + "blocklist.mlbf_generation_time": "Missing Date", + "blocklist.mlbf_stash_time_oldest": "Missing Date", + "blocklist.mlbf_stash_time_newest": "Missing Date", + }); +}); + +// Test the telemetry that would be recorded in the common case. +add_task(async function test_common_good_case_with_stashes() { + resetBlocklistTelemetry(); + // The exact content of the attachment does not matter in this test, as long + // as the data is valid. + await ExtensionBlocklistMLBF._client.db.saveAttachment( + ExtensionBlocklistMLBF.RS_ATTACHMENT_ID, + { record: MLBF_RECORD, blob: await load_mlbf_record_as_blob() } + ); + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: RECORDS_WITH_STASHES_AND_MLBF, + }); + Assert.equal("cache_match", testGetValue(Glean.blocklist.mlbfSource)); + Assert.equal( + MLBF_RECORD.generation_time, + testGetValue(Glean.blocklist.mlbfGenerationTime).getTime() + ); + Assert.equal( + OLDEST_STASH.stash_time, + testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime() + ); + Assert.equal( + NEWEST_STASH.stash_time, + testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime() + ); + assertTelemetryScalars({ + "blocklist.mlbf_source": "cache_match", + "blocklist.mlbf_generation_time": toUTC(MLBF_RECORD.generation_time), + "blocklist.mlbf_stash_time_oldest": toUTC(OLDEST_STASH.stash_time), + "blocklist.mlbf_stash_time_newest": toUTC(NEWEST_STASH.stash_time), + }); + + // The records and cached attachment carries over to the next tests. +}); + +// Test what happens when there are no stashes in the collection itself. +add_task(async function test_without_stashes() { + resetBlocklistTelemetry(); + await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [MLBF_RECORD] }); + + Assert.equal("cache_match", testGetValue(Glean.blocklist.mlbfSource)); + Assert.equal( + MLBF_RECORD.generation_time, + testGetValue(Glean.blocklist.mlbfGenerationTime).getTime() + ); + + Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime()); + Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime()); + + assertTelemetryScalars({ + "blocklist.mlbf_source": "cache_match", + "blocklist.mlbf_generation_time": toUTC(MLBF_RECORD.generation_time), + "blocklist.mlbf_stash_time_oldest": "Missing Date", + "blocklist.mlbf_stash_time_newest": "Missing Date", + }); +}); + +// Test what happens when the collection was inadvertently emptied, +// but still with a cached mlbf from before. +add_task(async function test_without_collection_but_cache() { + resetBlocklistTelemetry(); + await AddonTestUtils.loadBlocklistRawData({ + // Insert a dummy record with a value of last_modified which is higher than + // any value of last_modified in addons-bloomfilters.json, to prevent the + // blocklist implementation from automatically falling back to the packaged + // JSON dump. + extensionsMLBF: [{ last_modified: Date.now() }], + }); + Assert.equal("cache_fallback", testGetValue(Glean.blocklist.mlbfSource)); + Assert.equal( + MLBF_RECORD.generation_time, + testGetValue(Glean.blocklist.mlbfGenerationTime).getTime() + ); + + Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime()); + Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime()); + + assertTelemetryScalars({ + "blocklist.mlbf_source": "cache_fallback", + "blocklist.mlbf_generation_time": toUTC(MLBF_RECORD.generation_time), + "blocklist.mlbf_stash_time_oldest": "Missing Date", + "blocklist.mlbf_stash_time_newest": "Missing Date", + }); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js new file mode 100644 index 0000000000..b98d6e345d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * @fileOverview Checks that the MLBF updating logic works reasonably. + */ + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + +// This test needs to interact with the RemoteSettings client. +ExtensionBlocklistMLBF.ensureInitialized(); + +// Multiple internal calls to update should be coalesced and end up with the +// MLBF attachment from the last update call. +add_task(async function collapse_multiple_pending_update_requests() { + const observed = []; + + // The first step of starting an update is to read from the RemoteSettings + // collection. When a non-forced update is requested while another update is + // pending, the non-forced update should return/await the previous call + // instead of starting a new read/fetch from the RemoteSettings collection. + // Add a spy to the RemoteSettings client, so we can verify that the number + // of RemoteSettings accesses matches with what we expect. + const originalClientGet = ExtensionBlocklistMLBF._client.get; + const spyClientGet = (tag, returnValue) => { + ExtensionBlocklistMLBF._client.get = async function () { + // Record the method call. + observed.push(tag); + // Clone a valid record and tag it so we can identify it below. + let dummyRecord = JSON.parse(JSON.stringify(MLBF_RECORD)); + dummyRecord.tagged = tag; + return [dummyRecord]; + }; + }; + + // Another significant part of updating is fetching the MLBF attachment. + // Add a spy too, so we can check which attachment is being requested. + const originalFetchMLBF = ExtensionBlocklistMLBF._fetchMLBF; + ExtensionBlocklistMLBF._fetchMLBF = async function (record) { + observed.push(`fetchMLBF:${record.tagged}`); + throw new Error(`Deliberately ignoring call to MLBF:${record.tagged}`); + }; + + spyClientGet("initial"); // Very first call = read RS. + let update1 = ExtensionBlocklistMLBF._updateMLBF(false); + spyClientGet("unexpected update2"); // Non-forced update = reuse update1. + let update2 = ExtensionBlocklistMLBF._updateMLBF(false); + spyClientGet("forced1"); // forceUpdate=true = supersede previous update. + let forcedUpdate1 = ExtensionBlocklistMLBF._updateMLBF(true); + spyClientGet("forced2"); // forceUpdate=true = supersede previous update. + let forcedUpdate2 = ExtensionBlocklistMLBF._updateMLBF(true); + + let res = await Promise.all([update1, update2, forcedUpdate1, forcedUpdate2]); + + Assert.equal(observed.length, 4, "expected number of observed events"); + Assert.equal(observed[0], "initial", "First update should request records"); + Assert.equal(observed[1], "forced1", "Forced update supersedes initial"); + Assert.equal(observed[2], "forced2", "Forced update supersedes forced1"); + // We call the _updateMLBF methods immediately after each other. Every update + // request starts with an asynchronous operation (looking up the RS records), + // so the implementation should return early for all update requests except + // for the last one. So we should only observe a fetch for the last request. + Assert.equal(observed[3], "fetchMLBF:forced2", "expected fetch result"); + + // All update requests should end up with the same result. + Assert.equal(res[0], res[1], "update1 == update2"); + Assert.equal(res[1], res[2], "update2 == forcedUpdate1"); + Assert.equal(res[2], res[3], "forcedUpdate1 == forcedUpdate2"); + + ExtensionBlocklistMLBF._client.get = originalClientGet; + ExtensionBlocklistMLBF._fetchMLBF = originalFetchMLBF; +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js new file mode 100644 index 0000000000..243225c6e0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js @@ -0,0 +1,286 @@ +/* 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/. + */ + +// useMLBF=true only supports blocking by version+ID, not by OS/ABI. +enable_blocklist_v2_instead_of_useMLBF(); + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +const ADDONS = [ + { + id: "test_bug393285_1@tests.mozilla.org", + name: "extension 1", + version: "1.0", + + // No info in blocklist, shouldn't be blocked + notBlocklisted: [ + ["1", "1.9"], + [null, null], + ], + }, + { + id: "test_bug393285_2@tests.mozilla.org", + name: "extension 2", + version: "1.0", + + // Should always be blocked + blocklisted: [ + ["1", "1.9"], + [null, null], + ], + }, + { + id: "test_bug393285_3a@tests.mozilla.org", + name: "extension 3a", + version: "1.0", + + // Only version 1 should be blocked + blocklisted: [ + ["1", "1.9"], + [null, null], + ], + }, + { + id: "test_bug393285_3b@tests.mozilla.org", + name: "extension 3b", + version: "2.0", + + // Only version 1 should be blocked + notBlocklisted: [["1", "1.9"]], + }, + { + id: "test_bug393285_4@tests.mozilla.org", + name: "extension 4", + version: "1.0", + + // Should be blocked for app version 1 + blocklisted: [ + ["1", "1.9"], + [null, null], + ], + notBlocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_5@tests.mozilla.org", + name: "extension 5", + version: "1.0", + + // Not blocklisted because we are a different OS + notBlocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_6@tests.mozilla.org", + name: "extension 6", + version: "1.0", + + // Blocklisted based on OS + blocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_7@tests.mozilla.org", + name: "extension 7", + version: "1.0", + + // Blocklisted based on OS + blocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_8@tests.mozilla.org", + name: "extension 8", + version: "1.0", + + // Not blocklisted because we are a different ABI + notBlocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_9@tests.mozilla.org", + name: "extension 9", + version: "1.0", + + // Blocklisted based on ABI + blocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_10@tests.mozilla.org", + name: "extension 10", + version: "1.0", + + // Blocklisted based on ABI + blocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_11@tests.mozilla.org", + name: "extension 11", + version: "1.0", + + // Doesn't match both os and abi so not blocked + notBlocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_12@tests.mozilla.org", + name: "extension 12", + version: "1.0", + + // Doesn't match both os and abi so not blocked + notBlocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_13@tests.mozilla.org", + name: "extension 13", + version: "1.0", + + // Doesn't match both os and abi so not blocked + notBlocklisted: [["2", "1.9"]], + }, + { + id: "test_bug393285_14@tests.mozilla.org", + name: "extension 14", + version: "1.0", + + // Matches both os and abi so blocked + blocklisted: [["2", "1.9"]], + }, +]; + +const ADDON_IDS = ADDONS.map(a => a.id); + +const BLOCKLIST_DATA = [ + { + guid: "test_bug393285_2@tests.mozilla.org", + versionRange: [], + }, + { + guid: "test_bug393285_3a@tests.mozilla.org", + versionRange: [{ maxVersion: "1.0", minVersion: "1.0" }], + }, + { + guid: "test_bug393285_3b@tests.mozilla.org", + versionRange: [{ maxVersion: "1.0", minVersion: "1.0" }], + }, + { + guid: "test_bug393285_4@tests.mozilla.org", + versionRange: [ + { + maxVersion: "1.0", + minVersion: "1.0", + targetApplication: [ + { + guid: "xpcshell@tests.mozilla.org", + maxVersion: "1.0", + minVersion: "1.0", + }, + ], + }, + ], + }, + { + guid: "test_bug393285_5@tests.mozilla.org", + os: "Darwin", + versionRange: [], + }, + { + guid: "test_bug393285_6@tests.mozilla.org", + os: "XPCShell", + versionRange: [], + }, + { + guid: "test_bug393285_7@tests.mozilla.org", + os: "Darwin,XPCShell,WINNT", + versionRange: [], + }, + { + guid: "test_bug393285_8@tests.mozilla.org", + xpcomabi: "x86-msvc", + versionRange: [], + }, + { + guid: "test_bug393285_9@tests.mozilla.org", + xpcomabi: "noarch-spidermonkey", + versionRange: [], + }, + { + guid: "test_bug393285_10@tests.mozilla.org", + xpcomabi: "ppc-gcc3,noarch-spidermonkey,x86-msvc", + versionRange: [], + }, + { + guid: "test_bug393285_11@tests.mozilla.org", + os: "Darwin", + xpcomabi: "ppc-gcc3,x86-msvc", + versionRange: [], + }, + { + guid: "test_bug393285_12@tests.mozilla.org", + os: "Darwin", + xpcomabi: "ppc-gcc3,noarch-spidermonkey,x86-msvc", + versionRange: [], + }, + { + guid: "test_bug393285_13@tests.mozilla.org", + os: "XPCShell", + xpcomabi: "ppc-gcc3,x86-msvc", + versionRange: [], + }, + { + guid: "test_bug393285_14@tests.mozilla.org", + os: "XPCShell,WINNT", + xpcomabi: "ppc-gcc3,x86-msvc,noarch-spidermonkey", + versionRange: [], + }, +]; + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); + await promiseStartupManager(); + + for (let addon of ADDONS) { + await promiseInstallWebExtension({ + manifest: { + name: addon.name, + version: addon.version, + browser_specific_settings: { gecko: { id: addon.id } }, + }, + }); + } + + let addons = await getAddons(ADDON_IDS); + for (let id of ADDON_IDS) { + equal( + addons.get(id).blocklistState, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED, + `Add-on ${id} should not initially be blocked` + ); + } +}); + +add_task(async function test_1() { + await AddonTestUtils.loadBlocklistRawData({ extensions: BLOCKLIST_DATA }); + + let addons = await getAddons(ADDON_IDS); + async function isBlocklisted(addon, appVer, toolkitVer) { + let state = await Blocklist.getAddonBlocklistState( + addon, + appVer, + toolkitVer + ); + return state != Services.blocklist.STATE_NOT_BLOCKED; + } + for (let addon of ADDONS) { + let { id } = addon; + for (let blocklisted of addon.blocklisted || []) { + ok( + await isBlocklisted(addons.get(id), ...blocklisted), + `Add-on ${id} should be blocklisted in app/platform version ${blocklisted}` + ); + } + for (let notBlocklisted of addon.notBlocklisted || []) { + ok( + !(await isBlocklisted(addons.get(id), ...notBlocklisted)), + `Add-on ${id} should not be blocklisted in app/platform version ${notBlocklisted}` + ); + } + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js new file mode 100644 index 0000000000..42eb1305c4 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js @@ -0,0 +1,106 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests resetting of preferences in blocklist entry when an add-on is blocked. +// See bug 802434. + +// useMLBF=true only supports blocking, not resetting prefs, since extensions +// cannot set arbitrary prefs any more after the removal of legacy addons. +enable_blocklist_v2_instead_of_useMLBF(); + +const BLOCKLIST_DATA = [ + { + guid: "block1@tests.mozilla.org", + versionRange: [ + { + severity: "1", + targetApplication: [ + { + guid: "xpcshell@tests.mozilla.org", + maxVersion: "2.*", + minVersion: "1", + }, + ], + }, + ], + prefs: ["test.blocklist.pref1", "test.blocklist.pref2"], + }, + { + guid: "block2@tests.mozilla.org", + versionRange: [ + { + severity: "3", + targetApplication: [ + { + guid: "xpcshell@tests.mozilla.org", + maxVersion: "2.*", + minVersion: "1", + }, + ], + }, + ], + prefs: ["test.blocklist.pref3", "test.blocklist.pref4"], + }, +]; + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + + await promiseStartupManager(); + + // Add 2 extensions + await promiseInstallWebExtension({ + manifest: { + name: "Blocked add-on-1 with to-be-reset prefs", + version: "1.0", + browser_specific_settings: { gecko: { id: "block1@tests.mozilla.org" } }, + }, + }); + await promiseInstallWebExtension({ + manifest: { + name: "Blocked add-on-2 with to-be-reset prefs", + version: "1.0", + browser_specific_settings: { gecko: { id: "block2@tests.mozilla.org" } }, + }, + }); + + // Pre-set the preferences that we expect to get reset. + Services.prefs.setIntPref("test.blocklist.pref1", 15); + Services.prefs.setIntPref("test.blocklist.pref2", 15); + Services.prefs.setBoolPref("test.blocklist.pref3", true); + Services.prefs.setBoolPref("test.blocklist.pref4", true); + + // Before blocklist is loaded. + let [a1, a2] = await AddonManager.getAddonsByIDs([ + "block1@tests.mozilla.org", + "block2@tests.mozilla.org", + ]); + Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + Assert.equal(Services.prefs.getIntPref("test.blocklist.pref1"), 15); + Assert.equal(Services.prefs.getIntPref("test.blocklist.pref2"), 15); + Assert.equal(Services.prefs.getBoolPref("test.blocklist.pref3"), true); + Assert.equal(Services.prefs.getBoolPref("test.blocklist.pref4"), true); +}); + +add_task(async function test_blocks() { + await AddonTestUtils.loadBlocklistRawData({ extensions: BLOCKLIST_DATA }); + + // Blocklist changes should have applied and the prefs must be reset. + let [a1, a2] = await AddonManager.getAddonsByIDs([ + "block1@tests.mozilla.org", + "block2@tests.mozilla.org", + ]); + Assert.notEqual(a1, null); + Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + Assert.notEqual(a2, null); + Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED); + + // All these prefs must be reset to defaults. + Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref1"), false); + Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref2"), false); + Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref3"), false); + Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref4"), false); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js new file mode 100644 index 0000000000..f48a6b9d8b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js @@ -0,0 +1,225 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// useMLBF=true only supports blocking by version+ID, not by regexp. +enable_blocklist_v2_instead_of_useMLBF(); + +const BLOCKLIST_DATA = [ + { + guid: "/^abcd.*/", + versionRange: [], + expectedType: "RegExp", + }, + { + guid: "test@example.com", + versionRange: [], + expectedType: "string", + }, + { + guid: "/^((a)|(b)|(c))$/", + versionRange: [], + expectedType: "Set", + }, + { + guid: "/^((a@b)|(\\{6d9ddd6e-c6ee-46de-ab56-ce9080372b3\\})|(c@d.com))$/", + versionRange: [], + expectedType: "Set", + }, + // The same as the above, but with escape sequences that disqualify it from + // being treated as a set (and a different guid) + { + guid: "/^((s@t)|(\\{6d9eee6e-c6ee-46de-ab56-ce9080372b3\\})|(c@d\\w.com))$/", + versionRange: [], + expectedType: "RegExp", + }, + // Also the same, but with other magical regex characters. + // (and a different guid) + { + guid: "/^((u@v)|(\\{6d9fff6e*-c6ee-46de-ab56-ce9080372b3\\})|(c@dee?.com))$/", + versionRange: [], + expectedType: "RegExp", + }, +]; + +/** + * Verify that both IDs being OR'd in a regex work, + * and that other regular expressions continue being + * used as regular expressions. + */ +add_task(async function test_check_matching_works() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); + await promiseStartupManager(); + await AddonTestUtils.loadBlocklistRawData({ + extensions: BLOCKLIST_DATA, + }); + + const { BlocklistPrivate } = ChromeUtils.importESModule( + "resource://gre/modules/Blocklist.sys.mjs" + ); + let parsedEntries = BlocklistPrivate.ExtensionBlocklistRS._entries; + + // Unfortunately, the parsed entries aren't in the same order as the original. + function strForTypeOf(val) { + if (typeof val == "string") { + return "string"; + } + if (val) { + return val.constructor.name; + } + return "other"; + } + for (let type of ["Set", "RegExp", "string"]) { + let numberParsed = parsedEntries.filter(parsedEntry => { + return type == strForTypeOf(parsedEntry.matches.id); + }).length; + let expected = BLOCKLIST_DATA.filter(entry => { + return type == entry.expectedType; + }).length; + Assert.equal( + numberParsed, + expected, + type + " should have expected number of entries" + ); + } + // Shouldn't block everything. + Assert.ok( + !(await Blocklist.getAddonBlocklistEntry({ id: "nonsense", version: "1" })) + ); + // Should block IDs starting with abcd + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ id: "abcde", version: "1" }) + ); + // Should block the literal string listed + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ + id: "test@example.com", + version: "1", + }) + ); + // Should block the IDs in (a)|(b)|(c) + Assert.ok(await Blocklist.getAddonBlocklistEntry({ id: "a", version: "1" })); + Assert.ok(await Blocklist.getAddonBlocklistEntry({ id: "b", version: "1" })); + Assert.ok(await Blocklist.getAddonBlocklistEntry({ id: "c", version: "1" })); + // Should block all the items processed to a set: + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ id: "a@b", version: "1" }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ + id: "{6d9ddd6e-c6ee-46de-ab56-ce9080372b3}", + version: "1", + }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ id: "c@d.com", version: "1" }) + ); + // Should block items that remained a regex: + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ id: "s@t", version: "1" }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ + id: "{6d9eee6e-c6ee-46de-ab56-ce9080372b3}", + version: "1", + }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ id: "c@dx.com", version: "1" }) + ); + + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ id: "u@v", version: "1" }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ + id: "{6d9fff6eeeeeeee-c6ee-46de-ab56-ce9080372b3}", + version: "1", + }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ id: "c@dee.com", version: "1" }) + ); +}); + +// We should be checking all properties, not just the first one we come across. +add_task(async function check_all_properties() { + await AddonTestUtils.loadBlocklistRawData({ + extensions: [ + { + guid: "literal@string.com", + creator: "Foo", + versionRange: [], + }, + { + guid: "/regex.*@regex\\.com/", + creator: "Foo", + versionRange: [], + }, + { + guid: "/^((set@set\\.com)|(anotherset@set\\.com)|(reallyenoughsetsalready@set\\.com))$/", + creator: "Foo", + versionRange: [], + }, + ], + }); + + let { Blocklist } = ChromeUtils.importESModule( + "resource://gre/modules/Blocklist.sys.mjs" + ); + // Check 'wrong' creator doesn't match. + Assert.ok( + !(await Blocklist.getAddonBlocklistEntry({ + id: "literal@string.com", + version: "1", + creator: { name: "Bar" }, + })) + ); + Assert.ok( + !(await Blocklist.getAddonBlocklistEntry({ + id: "regexaaaaa@regex.com", + version: "1", + creator: { name: "Bar" }, + })) + ); + Assert.ok( + !(await Blocklist.getAddonBlocklistEntry({ + id: "set@set.com", + version: "1", + creator: { name: "Bar" }, + })) + ); + + // Check 'wrong' ID doesn't match. + Assert.ok( + !(await Blocklist.getAddonBlocklistEntry({ + id: "someotherid@foo.com", + version: "1", + creator: { name: "Foo" }, + })) + ); + + // Check items matching all filters do match + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ + id: "literal@string.com", + version: "1", + creator: { name: "Foo" }, + }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ + id: "regexaaaaa@regex.com", + version: "1", + creator: { name: "Foo" }, + }) + ); + Assert.ok( + await Blocklist.getAddonBlocklistEntry({ + id: "set@set.com", + version: "1", + creator: { name: "Foo" }, + }) + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js new file mode 100644 index 0000000000..fffbb8a51e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js @@ -0,0 +1,504 @@ +/* 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/. + */ + +// useMLBF=true only supports one type of severity (hard block). The value of +// appDisabled in the extension blocklist is checked in test_blocklist_mlbf.js. +enable_blocklist_v2_instead_of_useMLBF(); + +const URI_EXTENSION_BLOCKLIST_DIALOG = + "chrome://mozapps/content/extensions/blocklist.xhtml"; + +// Workaround for Bug 658720 - URL formatter can leak during xpcshell tests +const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; +Services.prefs.setCharPref( + PREF_BLOCKLIST_ITEM_URL, + "http://example.com/blocklist/%blockID%" +); + +async function getAddonBlocklistURL(addon) { + let entry = await Blocklist.getAddonBlocklistEntry(addon); + return entry && entry.url; +} + +var ADDONS = [ + { + // Tests how the blocklist affects a disabled add-on + id: "test_bug455906_1@tests.mozilla.org", + name: "Bug 455906 Addon Test 1", + version: "5", + appVersion: "3", + }, + { + // Tests how the blocklist affects an enabled add-on + id: "test_bug455906_2@tests.mozilla.org", + name: "Bug 455906 Addon Test 2", + version: "5", + appVersion: "3", + }, + { + // Tests how the blocklist affects an enabled add-on, to be disabled by the notification + id: "test_bug455906_3@tests.mozilla.org", + name: "Bug 455906 Addon Test 3", + version: "5", + appVersion: "3", + }, + { + // Tests how the blocklist affects a disabled add-on that was already warned about + id: "test_bug455906_4@tests.mozilla.org", + name: "Bug 455906 Addon Test 4", + version: "5", + appVersion: "3", + }, + { + // Tests how the blocklist affects an enabled add-on that was already warned about + id: "test_bug455906_5@tests.mozilla.org", + name: "Bug 455906 Addon Test 5", + version: "5", + appVersion: "3", + }, + { + // Tests how the blocklist affects an already blocked add-on + id: "test_bug455906_6@tests.mozilla.org", + name: "Bug 455906 Addon Test 6", + version: "5", + appVersion: "3", + }, + { + // Tests how the blocklist affects an incompatible add-on + id: "test_bug455906_7@tests.mozilla.org", + name: "Bug 455906 Addon Test 7", + version: "5", + appVersion: "2", + }, + { + // Spare add-on used to ensure we get a notification when switching lists + id: "dummy_bug455906_1@tests.mozilla.org", + name: "Dummy Addon 1", + version: "5", + appVersion: "3", + }, + { + // Spare add-on used to ensure we get a notification when switching lists + id: "dummy_bug455906_2@tests.mozilla.org", + name: "Dummy Addon 2", + version: "5", + appVersion: "3", + }, +]; + +var gNotificationCheck = null; + +// Don't need the full interface, attempts to call other methods will just +// throw which is just fine +var WindowWatcher = { + openWindow(parent, url, name, features, windowArguments) { + // Should be called to list the newly blocklisted items + equal(url, URI_EXTENSION_BLOCKLIST_DIALOG); + + if (gNotificationCheck) { + gNotificationCheck(windowArguments.wrappedJSObject); + } + + // run the code after the blocklist is closed + Services.obs.notifyObservers(null, "addon-blocklist-closed"); + }, + + QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]), +}; + +MockRegistrar.register( + "@mozilla.org/embedcomp/window-watcher;1", + WindowWatcher +); + +function createAddon(addon) { + return promiseInstallWebExtension({ + manifest: { + name: addon.name, + version: addon.version, + browser_specific_settings: { + gecko: { + id: addon.id, + strict_min_version: addon.appVersion, + strict_max_version: addon.appVersion, + }, + }, + }, + }); +} + +const BLOCKLIST_DATA = { + start: { + // Block 4-6 and a dummy: + extensions: [ + { + guid: "test_bug455906_4@tests.mozilla.org", + versionRange: [{ severity: "-1" }], + }, + { + guid: "test_bug455906_5@tests.mozilla.org", + versionRange: [{ severity: "1" }], + }, + { + guid: "test_bug455906_6@tests.mozilla.org", + versionRange: [{ severity: "2" }], + }, + { + guid: "dummy_bug455906_1@tests.mozilla.org", + versionRange: [], + }, + ], + }, + warn: { + // warn for all test add-ons: + extensions: ADDONS.filter(a => a.id.startsWith("test_")).map(a => ({ + guid: a.id, + versionRange: [{ severity: "-1" }], + })), + }, + block: { + // block all test add-ons: + extensions: ADDONS.filter(a => a.id.startsWith("test_")).map(a => ({ + guid: a.id, + blockID: a.id, + versionRange: [], + })), + }, + empty: { + // Block a dummy so there's a change: + extensions: [ + { + guid: "dummy_bug455906_2@tests.mozilla.org", + versionRange: [], + }, + ], + }, +}; + +async function loadBlocklist(id, callback) { + gNotificationCheck = callback; + + await AddonTestUtils.loadBlocklistRawData(BLOCKLIST_DATA[id]); +} + +function create_blocklistURL(blockID) { + let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); + url = url.replace(/%blockID%/g, blockID); + return url; +} + +// Before every main test this is the state the add-ons are meant to be in +async function checkInitialState() { + let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id)); + + checkAddonState(addons[0], { + userDisabled: true, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[1], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[2], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[3], { + userDisabled: true, + softDisabled: true, + appDisabled: false, + }); + checkAddonState(addons[4], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[5], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + checkAddonState(addons[6], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); +} + +function checkAddonState(addon, state) { + return checkAddon(addon.id, addon, state); +} + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "3"); + + await promiseStartupManager(); + + // Load the initial blocklist into the profile to check add-ons start in the + // right state. + await AddonTestUtils.loadBlocklistRawData(BLOCKLIST_DATA.start); + + for (let addon of ADDONS) { + await createAddon(addon); + } +}); + +add_task(async function test_1() { + // Tests the add-ons were installed and the initial blocklist applied as expected + + let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id)); + for (var i = 0; i < ADDONS.length; i++) { + ok(addons[i], `Addon ${i + 1} should be installed correctly`); + } + + checkAddonState(addons[0], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[1], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[2], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + + // Warn add-ons should be soft disabled automatically + checkAddonState(addons[3], { + userDisabled: true, + softDisabled: true, + appDisabled: false, + }); + checkAddonState(addons[4], { + userDisabled: true, + softDisabled: true, + appDisabled: false, + }); + + // Blocked and incompatible should be app disabled only + checkAddonState(addons[5], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + checkAddonState(addons[6], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + + // Put the add-ons into the base state + await addons[0].disable(); + await addons[4].enable(); + + await promiseRestartManager(); + await checkInitialState(); + + await loadBlocklist("warn", args => { + dump("Checking notification pt 2\n"); + // This test is artificial, we don't notify for add-ons anymore, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1257565#c111 . Cleaning this up + // should happen but this patchset is too huge as it is so I'm deferring it. + equal(args.list.length, 2); + }); + + await promiseRestartManager(); + dump("Checking results pt 2\n"); + + addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id)); + + info("Should have disabled this add-on as requested"); + checkAddonState(addons[2], { + userDisabled: true, + softDisabled: true, + appDisabled: false, + }); + + info("The blocked add-on should have changed to soft disabled"); + checkAddonState(addons[5], { + userDisabled: true, + softDisabled: true, + appDisabled: false, + }); + checkAddonState(addons[6], { + userDisabled: true, + softDisabled: true, + appDisabled: true, + }); + + info("These should have been unchanged"); + checkAddonState(addons[0], { + userDisabled: true, + softDisabled: false, + appDisabled: false, + }); + // XXXgijs this is supposed to be not user disabled or soft disabled, but because we don't show + // the dialog, it's disabled anyway. Comment out this assertion for now... + // checkAddonState(addons[1], {userDisabled: false, softDisabled: false, appDisabled: false}); + checkAddonState(addons[3], { + userDisabled: true, + softDisabled: true, + appDisabled: false, + }); + checkAddonState(addons[4], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + + // Back to starting state + await addons[2].enable(); + await addons[5].enable(); + + await promiseRestartManager(); + await loadBlocklist("start"); +}); + +add_task(async function test_pt3() { + await promiseRestartManager(); + await checkInitialState(); + + await loadBlocklist("block", args => { + dump("Checking notification pt 3\n"); + equal(args.list.length, 3); + }); + + await promiseRestartManager(); + dump("Checking results pt 3\n"); + + let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id)); + + // All should have gained the blocklist state, user disabled as previously + checkAddonState(addons[0], { + userDisabled: true, + softDisabled: false, + appDisabled: true, + }); + checkAddonState(addons[1], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + checkAddonState(addons[2], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + checkAddonState(addons[4], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + + // Should have gained the blocklist state but no longer be soft disabled + checkAddonState(addons[3], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + + // Check blockIDs are correct + equal( + await getAddonBlocklistURL(addons[0]), + create_blocklistURL(addons[0].id) + ); + equal( + await getAddonBlocklistURL(addons[1]), + create_blocklistURL(addons[1].id) + ); + equal( + await getAddonBlocklistURL(addons[2]), + create_blocklistURL(addons[2].id) + ); + equal( + await getAddonBlocklistURL(addons[3]), + create_blocklistURL(addons[3].id) + ); + equal( + await getAddonBlocklistURL(addons[4]), + create_blocklistURL(addons[4].id) + ); + + // Shouldn't be changed + checkAddonState(addons[5], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + checkAddonState(addons[6], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); + + // Back to starting state + await loadBlocklist("start"); +}); + +add_task(async function test_pt4() { + let addon = await AddonManager.getAddonByID(ADDONS[4].id); + await addon.enable(); + + await promiseRestartManager(); + await checkInitialState(); + + await loadBlocklist("empty", args => { + dump("Checking notification pt 4\n"); + // See note in other callback - we no longer notify for non-blocked add-ons. + ok(false, "Should not get a notification as there are no blocked addons."); + }); + + await promiseRestartManager(); + dump("Checking results pt 4\n"); + + let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id)); + // This should have become unblocked + checkAddonState(addons[5], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + + // Should get re-enabled + checkAddonState(addons[3], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + + // No change for anything else + checkAddonState(addons[0], { + userDisabled: true, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[1], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[2], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[4], { + userDisabled: false, + softDisabled: false, + appDisabled: false, + }); + checkAddonState(addons[6], { + userDisabled: false, + softDisabled: false, + appDisabled: true, + }); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js new file mode 100644 index 0000000000..ccaa1868fe --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js @@ -0,0 +1,411 @@ +// Verifies that changes to blocklistState are correctly reported to telemetry. + +"use strict"; + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + +// Set min version to 42 because the updater defaults to min version 42. +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0", "42.0"); + +// Use unprivileged signatures because the MLBF-based blocklist does not +// apply to add-ons with a privileged signature. +AddonTestUtils.usePrivilegedSignatures = false; + +const { Downloader } = ChromeUtils.importESModule( + "resource://services-settings/Attachments.sys.mjs" +); + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + +const EXT_ID = "maybeblockme@tests.mozilla.org"; + +// The addon blocked by the bloom filter (referenced by MLBF_RECORD). +const EXT_BLOCKED_ID = "@blocked"; +const EXT_BLOCKED_VERSION = "1"; +const EXT_BLOCKED_SIGN_TIME = 12345; // Before MLBF_RECORD.generation_time. + +// To serve updates. +const server = AddonTestUtils.createHttpServer(); +const SERVER_BASE_URL = `http://127.0.0.1:${server.identity.primaryPort}`; +const SERVER_UPDATE_PATH = "/update.json"; +const SERVER_UPDATE_URL = `${SERVER_BASE_URL}${SERVER_UPDATE_PATH}`; +// update is served via `server` over insecure http. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +async function assertEventDetails(expectedExtras) { + if (!IS_ANDROID_BUILD) { + const expectedEvents = expectedExtras.map(expectedExtra => { + let { object, value, ...extra } = expectedExtra; + return ["blocklist", "addonBlockChange", object, value, extra]; + }); + await TelemetryTestUtils.assertEvents(expectedEvents, { + category: "blocklist", + method: "addonBlockChange", + }); + } else { + info( + `Skip assertions on collected samples for addonBlockChange on android builds` + ); + } + assertGleanEventDetails(expectedExtras); +} +async function assertGleanEventDetails(expectedExtras) { + const snapshot = testGetValue(Glean.blocklist.addonBlockChange); + if (expectedExtras.length === 0) { + Assert.deepEqual(undefined, snapshot, "Expected zero addonBlockChange"); + return; + } + Assert.equal( + expectedExtras.length, + snapshot?.length, + "Number of addonBlockChange records" + ); + for (let i of expectedExtras.keys()) { + let actual = snapshot[i].extra; + // Glean uses snake_case instead of camelCase. + let { blocklistState, ...expected } = expectedExtras[i]; + expected.blocklist_state = blocklistState; + Assert.deepEqual(expected, actual, `Expected addonBlockChange (${i})`); + } +} + +// Stage an update on the update server. The add-on must have been created +// with update_url set to SERVER_UPDATE_URL. +function setupAddonUpdate(addonId, addonVersion) { + let updateXpi = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: addonVersion, + browser_specific_settings: { + gecko: { id: addonId, update_url: SERVER_UPDATE_URL }, + }, + }, + }); + let updateXpiPath = `/update-${addonId}-${addonVersion}.xpi`; + server.registerFile(updateXpiPath, updateXpi); + AddonTestUtils.registerJSON(server, SERVER_UPDATE_PATH, { + addons: { + [addonId]: { + updates: [ + { + version: addonVersion, + update_link: `${SERVER_BASE_URL}${updateXpiPath}`, + }, + ], + }, + }, + }); +} + +async function tryAddonInstall(addonId, addonVersion) { + let xpiFile = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: addonVersion, + browser_specific_settings: { + gecko: { id: addonId, update_url: SERVER_UPDATE_URL }, + }, + }, + }); + const install = await promiseInstallFile(xpiFile, true); + // Passing true to promiseInstallFile means that the xpi may not be installed + // if blocked by the blocklist. In that case, |install| may be null. + return install?.addon; +} + +add_task(async function setup() { + if (!IS_ANDROID_BUILD) { + // FOG needs a profile directory to put its data in. + do_get_profile(); + // FOG needs to be initialized in order for data to flow. + Services.fog.initializeFOG(); + } + await TelemetryController.testSetup(); + + // Disable the packaged record and attachment to make sure that the test + // will not fall back to the packaged attachments. + Downloader._RESOURCE_BASE_URL = "invalid://bogus"; + + await promiseStartupManager(); +}); + +add_task(async function install_update_not_blocked_is_no_events() { + resetBlocklistTelemetry(); + // Install an add-on that is not blocked. Then update to the next version. + let addon = await tryAddonInstall(EXT_ID, "0.1"); + + // Version "1" not blocked yet, but will be in the next test task. + setupAddonUpdate(EXT_ID, "1"); + let update = await AddonTestUtils.promiseFindAddonUpdates(addon); + await promiseCompleteInstall(update.updateAvailable); + addon = await AddonManager.getAddonByID(EXT_ID); + equal(addon.version, "1", "Add-on was updated"); + + await assertEventDetails([]); +}); + +add_task(async function blocklist_update_events() { + resetBlocklistTelemetry(); + const EXT_HOURS_SINCE_INSTALL = 4321; + const addon = await AddonManager.getAddonByID(EXT_ID); + addon.__AddonInternal__.installDate = + addon.installDate.getTime() - 3600000 * EXT_HOURS_SINCE_INSTALL; + + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [ + { stash: { blocked: [`${EXT_ID}:1`], unblocked: [] }, stash_time: 123 }, + { stash: { blocked: [`${EXT_ID}:2`], unblocked: [] }, stash_time: 456 }, + ], + }); + + await assertEventDetails([ + { + object: "blocklist_update", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "1", + signed_date: "0", + hours_since: `${EXT_HOURS_SINCE_INSTALL}`, + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +add_task(async function update_check_blocked_by_stash() { + resetBlocklistTelemetry(); + setupAddonUpdate(EXT_ID, "2"); + let addon = await AddonManager.getAddonByID(EXT_ID); + let update = await AddonTestUtils.promiseFindAddonUpdates(addon); + // Blocks in stashes are immediately enforced by update checks. + // Blocks stored in MLBFs are only enforced after the package is downloaded, + // and that scenario is covered by update_check_blocked_by_stash elsewhere. + equal(update.updateAvailable, false, "Update was blocked by stash"); + + await assertEventDetails([ + { + object: "addon_update_check", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "2", + signed_date: "0", + hours_since: "-1", + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +// Any attempt to re-install a blocked add-on should trigger a telemetry +// event, even though the blocklistState did not change. +add_task(async function reinstall_blocked_addon() { + resetBlocklistTelemetry(); + let blockedAddon = await AddonManager.getAddonByID(EXT_ID); + equal( + blockedAddon.blocklistState, + Ci.nsIBlocklistService.STATE_BLOCKED, + "Addon was initially blocked" + ); + + let addon = await tryAddonInstall(EXT_ID, "2"); + ok(!addon, "Add-on install should be blocked by a stash"); + + await assertEventDetails([ + { + // Note: installs of existing versions are observed as "addon_install". + // Only updates after update checks are tagged as "addon_update". + object: "addon_install", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "2", + signed_date: "0", + hours_since: "-1", + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +// For comparison with the next test task (database_modified), verify that a +// regular restart without database modifications does not trigger events. +add_task(async function regular_restart_no_event() { + resetBlocklistTelemetry(); + // Version different/higher than the 42.0 that was passed to createAppInfo at + // the start of this test file to force a database rebuild. + await promiseRestartManager("90.0"); + await assertEventDetails([]); + + await promiseRestartManager(); + await assertEventDetails([]); +}); + +add_task(async function database_modified() { + resetBlocklistTelemetry(); + const EXT_HOURS_SINCE_INSTALL = 3; + await promiseShutdownManager(); + + // Modify the addon database: blocked->not blocked + decrease installDate. + let addonDB = await IOUtils.readJSON(gExtensionsJSON.path); + let rawAddon = addonDB.addons[0]; + equal(rawAddon.id, EXT_ID, "Expected entry in addonDB"); + equal(rawAddon.blocklistState, 2, "Expected STATE_BLOCKED"); + rawAddon.blocklistState = 0; // STATE_NOT_BLOCKED + rawAddon.installDate = Date.now() - 3600000 * EXT_HOURS_SINCE_INSTALL; + await IOUtils.writeJSON(gExtensionsJSON.path, addonDB); + + // Bump version to force database rebuild. + await promiseStartupManager("91.0"); + // Shut down because the database reconcilation blocks shutdown, and we want + // to be certain that the process has finished before checking the events. + await promiseShutdownManager(); + await assertEventDetails([ + { + object: "addon_db_modified", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "1", + signed_date: "0", + hours_since: `${EXT_HOURS_SINCE_INSTALL}`, + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); + + resetBlocklistTelemetry(); + await promiseStartupManager(); + await assertEventDetails([]); +}); + +add_task(async function install_replaces_blocked_addon() { + resetBlocklistTelemetry(); + let addon = await tryAddonInstall(EXT_ID, "3"); + ok(addon, "Update supersedes blocked add-on"); + + await assertEventDetails([ + { + object: "addon_install", + value: EXT_ID, + blocklistState: "0", // Ci.nsIBlocklistService.STATE_NOT_BLOCKED + addon_version: "3", + signed_date: "0", + hours_since: "-1", + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +add_task(async function install_blocked_by_mlbf() { + resetBlocklistTelemetry(); + await ExtensionBlocklistMLBF._client.db.saveAttachment( + ExtensionBlocklistMLBF.RS_ATTACHMENT_ID, + { record: MLBF_RECORD, blob: await load_mlbf_record_as_blob() } + ); + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [MLBF_RECORD], + }); + + AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME; + let addon = await tryAddonInstall(EXT_BLOCKED_ID, EXT_BLOCKED_VERSION); + AddonTestUtils.certSignatureDate = null; + + ok(!addon, "Add-on install should be blocked by the MLBF"); + + await assertEventDetails([ + { + object: "addon_install", + value: EXT_BLOCKED_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: EXT_BLOCKED_VERSION, + signed_date: `${EXT_BLOCKED_SIGN_TIME}`, + hours_since: "-1", + // When there is no stash at all, the MLBF's generation_time is used. + mlbf_last_time: `${MLBF_RECORD.generation_time}`, + mlbf_generation: `${MLBF_RECORD.generation_time}`, + mlbf_source: "cache_match", + }, + ]); +}); + +// A limitation of the MLBF-based blocklist is that it needs the add-on package +// in order to check its signature date. +// This part of the test verifies that installation of the add-on is blocked, +// despite the update check tentatively accepting the package. +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1649896 for rationale. +add_task(async function update_check_blocked_by_mlbf() { + resetBlocklistTelemetry(); + // Install a version that we can update, lower than EXT_BLOCKED_VERSION. + let addon = await tryAddonInstall(EXT_BLOCKED_ID, "0.1"); + + setupAddonUpdate(EXT_BLOCKED_ID, EXT_BLOCKED_VERSION); + AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME; + let update = await AddonTestUtils.promiseFindAddonUpdates(addon); + ok(update.updateAvailable, "Update was not blocked by stash"); + + await promiseCompleteInstall(update.updateAvailable); + AddonTestUtils.certSignatureDate = null; + + addon = await AddonManager.getAddonByID(EXT_BLOCKED_ID); + equal(addon.version, EXT_BLOCKED_VERSION, "Add-on was updated"); + equal( + addon.blocklistState, + Ci.nsIBlocklistService.STATE_BLOCKED, + "Add-on is blocked" + ); + equal(addon.appDisabled, true, "Add-on was disabled because of the block"); + + await assertEventDetails([ + { + object: "addon_update", + value: EXT_BLOCKED_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: EXT_BLOCKED_VERSION, + signed_date: `${EXT_BLOCKED_SIGN_TIME}`, + hours_since: "-1", + mlbf_last_time: `${MLBF_RECORD.generation_time}`, + mlbf_generation: `${MLBF_RECORD.generation_time}`, + mlbf_source: "cache_match", + }, + ]); +}); + +add_task(async function update_blocked_to_unblocked() { + resetBlocklistTelemetry(); + // was blocked in update_check_blocked_by_mlbf. + let blockedAddon = await AddonManager.getAddonByID(EXT_BLOCKED_ID); + + // 3 is higher than EXT_BLOCKED_VERSION. + setupAddonUpdate(EXT_BLOCKED_ID, "3"); + AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME; + let update = await AddonTestUtils.promiseFindAddonUpdates(blockedAddon); + ok(update.updateAvailable, "Found an update"); + + await promiseCompleteInstall(update.updateAvailable); + AddonTestUtils.certSignatureDate = null; + + let addon = await AddonManager.getAddonByID(EXT_BLOCKED_ID); + equal(addon.appDisabled, false, "Add-on was re-enabled after unblock"); + await assertEventDetails([ + { + object: "addon_update", + value: EXT_BLOCKED_ID, + blocklistState: "0", // Ci.nsIBlocklistService.STATE_NOT_BLOCKED + addon_version: "3", + signed_date: `${EXT_BLOCKED_SIGN_TIME}`, + hours_since: "-1", + mlbf_last_time: `${MLBF_RECORD.generation_time}`, + mlbf_generation: `${MLBF_RECORD.generation_time}`, + mlbf_source: "cache_match", + }, + ]); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js new file mode 100644 index 0000000000..b48700570e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js @@ -0,0 +1,392 @@ +const { BlocklistPrivate } = ChromeUtils.importESModule( + "resource://gre/modules/Blocklist.sys.mjs" +); +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); + +const APP_ID = "xpcshell@tests.mozilla.org"; +const TOOLKIT_ID = "toolkit@mozilla.org"; + +let client; + +async function clear_state() { + // Clear local DB. + await client.db.clear(); +} + +async function createRecords(records) { + const withId = records.map((record, i) => ({ + id: `record-${i}`, + ...record, + })); + // Prevent packaged dump to be loaded with high collection timestamp + return client.db.importChanges({}, Date.now(), withId); +} + +function run_test() { + AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "58", + "" + ); + // This will initialize the remote settings clients for blocklists, + // with their specific options etc. + BlocklistPrivate.ExtensionBlocklistRS.ensureInitialized(); + // Obtain one of the instantiated client for our tests. + client = RemoteSettings("addons", { bucketName: "blocklists" }); + + run_next_test(); +} + +add_task(async function test_supports_filter_expressions() { + await createRecords([ + { + name: "My Extension", + filter_expression: 'env.appinfo.ID == "xpcshell@tests.mozilla.org"', + }, + { + name: "My Extension", + filter_expression: "1 == 2", + }, + ]); + + const list = await client.get(); + equal(list.length, 1); +}); +add_task(clear_state); + +add_task(async function test_returns_all_without_target() { + await createRecords([ + { + name: "My Extension", + }, + { + name: "foopydoo", + versionRange: [], + }, + { + name: "My Other Extension", + versionRange: [ + { + severity: 0, + targetApplication: [], + }, + ], + }, + { + name: "Java(\\(TM\\))? Plug-in 11\\.(7[6-9]|[8-9]\\d|1([0-6]\\d|70))(\\.\\d+)?([^\\d\\._]|$)", + versionRange: [ + { + severity: 0, + }, + ], + matchFilename: "libnpjp2\\.so", + }, + { + name: "foopydoo", + versionRange: [ + { + targetApplication: [], + maxVersion: "1", + minVersion: "0", + severity: "1", + }, + ], + }, + ]); + + const list = await client.get(); + equal(list.length, 5); +}); +add_task(clear_state); + +add_task(async function test_returns_without_guid_or_with_matching_guid() { + await createRecords([ + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [{}], + }, + ], + }, + { + willMatch: false, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: "some-guid", + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: TOOLKIT_ID, + }, + ], + }, + ], + }, + ]); + + const list = await client.get(); + info(JSON.stringify(list, null, 2)); + equal(list.length, 3); + ok(list.every(e => e.willMatch)); +}); +add_task(clear_state); + +add_task( + async function test_returns_without_app_version_or_with_matching_version() { + await createRecords([ + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + minVersion: "0", + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + minVersion: "0", + maxVersion: "9999", + }, + ], + }, + ], + }, + { + willMatch: false, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + minVersion: "0", + maxVersion: "1", + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: TOOLKIT_ID, + minVersion: "0", + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: TOOLKIT_ID, + minVersion: "0", + maxVersion: "9999", + }, + ], + }, + ], + // We can't test the false case with maxVersion for toolkit, because the toolkit version + // is 0 in xpcshell. + }, + ]); + + const list = await client.get(); + equal(list.length, 5); + ok(list.every(e => e.willMatch)); + } +); +add_task(clear_state); + +add_task(async function test_multiple_version_and_target_applications() { + await createRecords([ + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: "other-guid", + }, + ], + }, + { + targetApplication: [ + { + guid: APP_ID, + minVersion: "0", + maxVersion: "*", + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: "other-guid", + }, + ], + }, + { + targetApplication: [ + { + guid: APP_ID, + minVersion: "0", + }, + ], + }, + ], + }, + { + willMatch: false, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + maxVersion: "57.*", + }, + ], + }, + { + targetApplication: [ + { + guid: APP_ID, + maxVersion: "56.*", + }, + { + guid: APP_ID, + maxVersion: "57.*", + }, + ], + }, + ], + }, + ]); + + const list = await client.get(); + equal(list.length, 2); + ok(list.every(e => e.willMatch)); +}); +add_task(clear_state); + +add_task(async function test_complex_version() { + await createRecords([ + { + willMatch: false, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + maxVersion: "57.*", + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + maxVersion: "9999.*", + }, + ], + }, + ], + }, + { + willMatch: true, + name: "foopydoo", + versionRange: [ + { + targetApplication: [ + { + guid: APP_ID, + minVersion: "19.0a1", + }, + ], + }, + ], + }, + ]); + + const list = await client.get(); + equal(list.length, 2); +}); +add_task(clear_state); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js new file mode 100644 index 0000000000..cf1992b121 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js @@ -0,0 +1,138 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +AddonTestUtils.init(this); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "49" +); + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +add_setup({ skip_if: () => IS_ANDROID_BUILD }, function test_setup() { + // FOG needs a profile directory to put its data in. + do_get_profile(); + + // FOG needs to be initialized in order for data to flow. + Services.fog.initializeFOG(); +}); + +function assertTelemetryScalars(expectedScalars) { + if (!IS_ANDROID_BUILD) { + let scalars = TelemetryTestUtils.getProcessScalars("parent"); + + for (const scalarName of Object.keys(expectedScalars || {})) { + equal( + scalars[scalarName], + expectedScalars[scalarName], + `Got the expected value for ${scalarName} scalar` + ); + } + } else { + info( + `Skip assertions on collected samples for ${expectedScalars} on android builds` + ); + } +} + +add_task(async function test_setup() { + // Ensure that the telemetry scalar definitions are loaded and the + // AddonManager initialized. + await TelemetryController.testSetup(); + await AddonTestUtils.promiseStartupManager(); +}); + +add_task(async function test_blocklist_lastModified_rs_scalars() { + resetBlocklistTelemetry(); + const now = Date.now(); + + const lastEntryTimes = { + addons: now - 5000, + addons_mlbf: now - 4000, + }; + + const lastEntryTimesUTC = {}; + const toUTC = t => new Date(t).toUTCString(); + for (const key of Object.keys(lastEntryTimes)) { + lastEntryTimesUTC[key] = toUTC(lastEntryTimes[key]); + } + + const { + BlocklistPrivate: { + BlocklistTelemetry, + ExtensionBlocklistMLBF, + ExtensionBlocklistRS, + }, + } = ChromeUtils.importESModule("resource://gre/modules/Blocklist.sys.mjs"); + + // Return a promise resolved when the recordRSBlocklistLastModified method + // has been called (by temporarily replacing the method with a function that + // calls the real method and then resolve the promise). + function promiseScalarRecorded() { + return new Promise(resolve => { + let origFn = BlocklistTelemetry.recordRSBlocklistLastModified; + BlocklistTelemetry.recordRSBlocklistLastModified = async (...args) => { + BlocklistTelemetry.recordRSBlocklistLastModified = origFn; + let res = await origFn.apply(BlocklistTelemetry, args); + resolve(); + return res; + }; + }); + } + + async function fakeRemoteSettingsSync(rsClient, lastModified) { + await rsClient.db.importChanges({}, lastModified); + await rsClient.emit("sync"); + } + + assertTelemetryScalars({ + "blocklist.lastModified_rs_addons_mlbf": undefined, + }); + Assert.equal( + undefined, + testGetValue(Glean.blocklist.lastModifiedRsAddonsMblf) + ); + + info("Test RS addon blocklist lastModified scalar"); + + await ExtensionBlocklistRS.ensureInitialized(); + await Promise.all([ + promiseScalarRecorded(), + fakeRemoteSettingsSync(ExtensionBlocklistRS._client, lastEntryTimes.addons), + ]); + + assertTelemetryScalars({ + "blocklist.lastModified_rs_addons_mlbf": undefined, + }); + + Assert.equal( + undefined, + testGetValue(Glean.blocklist.lastModifiedRsAddonsMblf) + ); + + await ExtensionBlocklistMLBF.ensureInitialized(); + await Promise.all([ + promiseScalarRecorded(), + fakeRemoteSettingsSync( + ExtensionBlocklistMLBF._client, + lastEntryTimes.addons_mlbf + ), + ]); + + assertTelemetryScalars({ + "blocklist.lastModified_rs_addons_mlbf": lastEntryTimesUTC.addons_mlbf, + }); + Assert.equal( + new Date(lastEntryTimesUTC.addons_mlbf).getTime(), + testGetValue(Glean.blocklist.lastModifiedRsAddonsMblf).getTime() + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js new file mode 100644 index 0000000000..cd80b34ac0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js @@ -0,0 +1,1410 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that changes that cause an add-on to become unblocked or blocked have +// the right effect + +// The tests follow a mostly common pattern. First they start with the add-ons +// unblocked, then they make a change that causes the add-ons to become blocked +// then they make a similar change that keeps the add-ons blocked then they make +// a change that unblocks the add-ons. Some tests skip the initial part and +// start with add-ons detected as blocked. + +// softblock1 is enabled/disabled by the blocklist changes so its softDisabled +// property should always match its userDisabled property + +// softblock2 gets manually enabled then disabled after it becomes blocked so +// its softDisabled property should never become true after that + +// softblock3 does the same as softblock2 however it remains disabled + +// softblock4 is disabled while unblocked and so should never have softDisabled +// set to true and stay userDisabled. This add-on is not used in tests that +// start with add-ons blocked as it would be identical to softblock3 + +const URI_EXTENSION_BLOCKLIST_DIALOG = + "chrome://mozapps/content/extensions/blocklist.xhtml"; + +// Allow insecure updates +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); + +const IS_ANDROID_WITH_BLOCKLIST_V2 = + AppConstants.platform == "android" && !AppConstants.NIGHTLY_BUILD; + +// This is the initial value of Blocklist.allowDeprecatedBlocklistV2. +if (IS_ANDROID_WITH_BLOCKLIST_V2) { + // test_blocklistchange_v2.js tests blocklist v2, so we should flip the pref + // to enable the v3 blocklist on Android. + Assert.ok( + _TEST_NAME.includes("test_blocklistchange"), + `Expected _TEST_NAME to be test_blocklistchange{,_v2}.js` + ); + if (_TEST_NAME.includes("test_blocklistchange.js")) { + Assert.equal( + Services.prefs.getBoolPref("extensions.blocklist.useMLBF"), + false, + "Blocklist v3 disabled by default on Android" + ); + Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + } +} + +// TODO bug 1649906: strip blocklist v2-specific parts of this test. +// All specific logic is already covered by other test files, but the tests +// here trigger the logic via higher-level methods, so it may make sense to +// keep this file even after the removal of blocklist v2. +const useMLBF = Services.prefs.getBoolPref( + "extensions.blocklist.useMLBF", + true +); + +var testserver = createHttpServer({ hosts: ["example.com"] }); + +function permissionPromptHandler(subject, topic, data) { + ok( + subject?.wrappedJSObject?.info?.resolve, + "Got a permission prompt notification as expected" + ); + subject.wrappedJSObject.info.resolve(); +} + +Services.obs.addObserver( + permissionPromptHandler, + "webextension-permission-prompt" +); + +registerCleanupFunction(() => { + Services.obs.removeObserver( + permissionPromptHandler, + "webextension-permission-prompt" + ); +}); + +const XPIS = {}; + +const ADDON_IDS = [ + "softblock1@tests.mozilla.org", + "softblock2@tests.mozilla.org", + "softblock3@tests.mozilla.org", + "softblock4@tests.mozilla.org", + "hardblock@tests.mozilla.org", + "regexpblock@tests.mozilla.org", +]; + +const BLOCK_APP = [ + { + guid: "xpcshell@tests.mozilla.org", + maxVersion: "2.*", + minVersion: "2", + }, +]; +// JEXL filter expression that matches BLOCK_APP. +const BLOCK_APP_FILTER_EXPRESSION = `env.appinfo.ID == "xpcshell@tests.mozilla.org" && env.appinfo.version >= "2" && env.appinfo.version < "3"`; + +function softBlockApp(id) { + return { + guid: `${id}@tests.mozilla.org`, + versionRange: [ + { + severity: "1", + targetApplication: BLOCK_APP, + }, + ], + }; +} + +function softBlockAddonChange(id) { + return { + guid: `${id}@tests.mozilla.org`, + versionRange: [ + { + severity: "1", + minVersion: "2", + maxVersion: "3", + }, + ], + }; +} + +function softBlockUpdate2(id) { + return { + guid: `${id}@tests.mozilla.org`, + versionRange: [{ severity: "1" }], + }; +} + +function softBlockManual(id) { + return { + guid: `${id}@tests.mozilla.org`, + versionRange: [ + { + maxVersion: "2", + minVersion: "1", + severity: "1", + }, + ], + }; +} + +const BLOCKLIST_DATA = { + empty_blocklist: [], + app_update: [ + softBlockApp("softblock1"), + softBlockApp("softblock2"), + softBlockApp("softblock3"), + softBlockApp("softblock4"), + { + guid: "hardblock@tests.mozilla.org", + versionRange: [ + { + targetApplication: BLOCK_APP, + }, + ], + }, + { + guid: "/^RegExp/", + versionRange: [ + { + severity: "1", + targetApplication: BLOCK_APP, + }, + ], + }, + { + guid: "/^RegExp/i", + versionRange: [ + { + targetApplication: BLOCK_APP, + }, + ], + }, + ], + addon_change: [ + softBlockAddonChange("softblock1"), + softBlockAddonChange("softblock2"), + softBlockAddonChange("softblock3"), + softBlockAddonChange("softblock4"), + { + guid: "hardblock@tests.mozilla.org", + versionRange: [ + { + maxVersion: "3", + minVersion: "2", + }, + ], + }, + { + _comment: + "Two RegExp matches, so test flags work - first shouldn't match.", + guid: "/^RegExp/", + versionRange: [ + { + maxVersion: "3", + minVersion: "2", + severity: "1", + }, + ], + }, + { + guid: "/^RegExp/i", + versionRange: [ + { + maxVersion: "3", + minVersion: "2", + severity: "2", + }, + ], + }, + ], + blocklist_update2: [ + softBlockUpdate2("softblock1"), + softBlockUpdate2("softblock2"), + softBlockUpdate2("softblock3"), + softBlockUpdate2("softblock4"), + { + guid: "hardblock@tests.mozilla.org", + versionRange: [], + }, + { + guid: "/^RegExp/", + versionRange: [{ severity: "1" }], + }, + { + guid: "/^RegExp/i", + versionRange: [], + }, + ], + manual_update: [ + softBlockManual("softblock1"), + softBlockManual("softblock2"), + softBlockManual("softblock3"), + softBlockManual("softblock4"), + { + guid: "hardblock@tests.mozilla.org", + versionRange: [ + { + maxVersion: "2", + minVersion: "1", + }, + ], + }, + { + guid: "/^RegExp/i", + versionRange: [ + { + maxVersion: "2", + minVersion: "1", + }, + ], + }, + ], +}; + +// Blocklist v3 (useMLBF) only supports hard blocks by guid+version. Version +// ranges, regexps and soft blocks are not supported. So adjust expectations to +// ensure that the test passes even if useMLBF=true, by: +// - soft blocks are converted to hard blocks. +// - hard blocks are accepted as-is. +// - regexps blocks are converted to hard blocks. +// - Version ranges are expanded to cover all known versions. +if (useMLBF) { + for (let [key, blocks] of Object.entries(BLOCKLIST_DATA)) { + BLOCKLIST_DATA[key] = []; + for (let block of blocks) { + let { guid } = block; + if (guid.includes("RegExp")) { + guid = "regexpblock@tests.mozilla.org"; + } else if (!guid.startsWith("soft") && !guid.startsWith("hard")) { + throw new Error(`Unexpected mock addon ID: ${guid}`); + } + + const { + minVersion = "1", + maxVersion = "3", + targetApplication, + } = block.versionRange?.[0] || {}; + + for (let v = minVersion; v <= maxVersion; ++v) { + BLOCKLIST_DATA[key].push({ + // Assume that IF targetApplication is set, that it is BLOCK_APP. + filter_expression: targetApplication && BLOCK_APP_FILTER_EXPRESSION, + stash: { + // XPI files use version `${v}.0`, update manifests use `${v}`. + blocked: [`${guid}:${v}.0`, `${guid}:${v}`], + unblocked: [], + }, + }); + } + } + } +} + +// XXXgijs: according to https://bugzilla.mozilla.org/show_bug.cgi?id=1257565#c111 +// this code and the related code in Blocklist.jsm (specific to XML blocklist) is +// dead code and can be removed. See https://bugzilla.mozilla.org/show_bug.cgi?id=1549550 . +// +// Don't need the full interface, attempts to call other methods will just +// throw which is just fine +var WindowWatcher = { + openWindow(parent, url, name, features, openArgs) { + // Should be called to list the newly blocklisted items + Assert.equal(url, URI_EXTENSION_BLOCKLIST_DIALOG); + + // Simulate auto-disabling any softblocks + var list = openArgs.wrappedJSObject.list; + list.forEach(function (aItem) { + if (!aItem.blocked) { + aItem.disable = true; + } + }); + + // run the code after the blocklist is closed + Services.obs.notifyObservers(null, "addon-blocklist-closed"); + }, + + QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]), +}; + +MockRegistrar.register( + "@mozilla.org/embedcomp/window-watcher;1", + WindowWatcher +); + +var InstallConfirm = { + confirm(aWindow, aUrl, aInstalls) { + aInstalls.forEach(function (aInstall) { + aInstall.install(); + }); + }, + + QueryInterface: ChromeUtils.generateQI(["amIWebInstallPrompt"]), +}; + +var InstallConfirmFactory = { + createInstance: function createInstance(iid) { + return InstallConfirm.QueryInterface(iid); + }, +}; + +var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); +registrar.registerFactory( + Components.ID("{f0863905-4dde-42e2-991c-2dc8209bc9ca}"), + "Fake Install Prompt", + "@mozilla.org/addons/web-install-prompt;1", + InstallConfirmFactory +); + +function Pload_blocklist(aId) { + return AddonTestUtils.loadBlocklistRawData({ + [useMLBF ? "extensionsMLBF" : "extensions"]: BLOCKLIST_DATA[aId], + }); +} + +// Does a background update check for add-ons and returns a promise that +// resolves when any started installs complete +function Pbackground_update() { + return new Promise((resolve, reject) => { + let installCount = 0; + let backgroundCheckCompleted = false; + + AddonManager.addInstallListener({ + onNewInstall(aInstall) { + installCount++; + }, + + onInstallEnded(aInstall) { + installCount--; + // Wait until all started installs have completed + if (installCount) { + return; + } + + AddonManager.removeInstallListener(this); + + // If the background check hasn't yet completed then let that call the + // callback when it is done + if (!backgroundCheckCompleted) { + return; + } + + resolve(); + }, + }); + + Services.obs.addObserver(function observer() { + Services.obs.removeObserver( + observer, + "addons-background-update-complete" + ); + backgroundCheckCompleted = true; + + // If any new installs have started then we'll call the callback once they + // are completed + if (installCount) { + return; + } + + resolve(); + }, "addons-background-update-complete"); + + AddonManagerPrivate.backgroundUpdateCheck(); + }); +} + +// Manually updates the test add-ons to the given version +function Pmanual_update(aVersion) { + const names = ["soft1", "soft2", "soft3", "soft4", "hard", "regexp"]; + return Promise.all( + names.map(async name => { + let url = `http://example.com/addons/blocklist_${name}_${aVersion}.xpi`; + let install = await AddonManager.getInstallForURL(url); + + // installAddonFromAOM() does more checking than install.install(). + // In particular, it will refuse to install an incompatible addon. + + return new Promise(resolve => { + install.addListener({ + onDownloadCancelled: resolve, + onInstallEnded: resolve, + }); + + AddonManager.installAddonFromAOM(null, null, install); + }); + }) + ); +} + +// Checks that an add-ons properties match expected values +function check_addon( + aAddon, + aExpectedVersion, + aExpectedUserDisabled, + aExpectedSoftDisabled, + aExpectedState +) { + if (useMLBF) { + if (aAddon.id.startsWith("soft")) { + if (aExpectedState === Ci.nsIBlocklistService.STATE_SOFTBLOCKED) { + // The whole test file assumes that an add-on is "user-disabled" after + // an explicit disable(), or after a soft block (without enable()). + // With useMLBF, soft blocks are not supported, so the "user-disabled" + // state matches the usual behavior of "userDisabled" (=disable()). + aExpectedUserDisabled = aAddon.userDisabled; + aExpectedSoftDisabled = false; + aExpectedState = Ci.nsIBlocklistService.STATE_BLOCKED; + } + } + } + + Assert.notEqual(aAddon, null); + info( + "Testing " + + aAddon.id + + " version " + + aAddon.version + + " user " + + aAddon.userDisabled + + " soft " + + aAddon.softDisabled + + " perms " + + aAddon.permissions + ); + + Assert.equal(aAddon.version, aExpectedVersion); + Assert.equal(aAddon.blocklistState, aExpectedState); + Assert.equal(aAddon.userDisabled, aExpectedUserDisabled); + Assert.equal(aAddon.softDisabled, aExpectedSoftDisabled); + if (aAddon.softDisabled) { + Assert.ok(aAddon.userDisabled); + } + + if (aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED) { + info("blocked, PERM_CAN_ENABLE " + aAddon.id); + Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE)); + info("blocked, PERM_CAN_DISABLE " + aAddon.id); + Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE)); + } else if (aAddon.userDisabled) { + info("userDisabled, PERM_CAN_ENABLE " + aAddon.id); + Assert.ok(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE)); + info("userDisabled, PERM_CAN_DISABLE " + aAddon.id); + Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE)); + } else { + info("other, PERM_CAN_ENABLE " + aAddon.id); + Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE)); + if (aAddon.type != "theme") { + info("other, PERM_CAN_DISABLE " + aAddon.id); + Assert.ok(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE)); + } + } + Assert.equal( + aAddon.appDisabled, + aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED + ); + + let willBeActive = aAddon.isActive; + if (hasFlag(aAddon.pendingOperations, AddonManager.PENDING_DISABLE)) { + willBeActive = false; + } else if (hasFlag(aAddon.pendingOperations, AddonManager.PENDING_ENABLE)) { + willBeActive = true; + } + + if ( + aExpectedUserDisabled || + aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED + ) { + Assert.ok(!willBeActive); + } else { + Assert.ok(willBeActive); + } +} + +async function promiseRestartManagerWithAppChange(version) { + await promiseShutdownManager(); + await promiseStartupManagerWithAppChange(version); +} + +async function promiseStartupManagerWithAppChange(version) { + if (version) { + AddonTestUtils.appInfo.version = version; + } + if (useMLBF) { + // The old ExtensionBlocklist enforced the app version/ID part of the block + // when the blocklist entry is checked. + // The new ExtensionBlocklist (with useMLBF=true) does not separately check + // the app version/ID, but the underlying data source (Remote Settings) + // does offer the ability to filter entries with `filter_expression`. + // Force a reload to ensure that the BLOCK_APP_FILTER_EXPRESSION filter in + // this test file is checked again against the new version. + await Blocklist.ExtensionBlocklist._updateMLBF(); + } + await promiseStartupManager(); +} + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + if (useMLBF) { + const { ClientEnvironmentBase } = ChromeUtils.importESModule( + "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs" + ); + Object.defineProperty(ClientEnvironmentBase, "appinfo", { + configurable: true, + get() { + return gAppInfo; + }, + }); + } + + function getxpibasename(id, version) { + // pattern used to map ids like softblock1 to soft1 + let pattern = /^(soft|hard|regexp)block([1-9]*)@/; + let match = id.match(pattern); + return `blocklist_${match[1]}${match[2]}_${version}`; + } + for (let id of ADDON_IDS) { + for (let version of [1, 2, 3, 4]) { + let name = getxpibasename(id, version); + + let xpi = createTempWebExtensionFile({ + manifest: { + name: "Test", + version: `${version}.0`, + browser_specific_settings: { + gecko: { + id, + // This file is generated below, as updateJson. + update_url: `http://example.com/addon_update${version}.json`, + }, + }, + }, + }); + + // To test updates, individual tasks in this test file start the test by + // installing a set of add-ons with version |version| and trigger an + // update check, from XPIS.${nameprefix}${version} (version = 1, 2, 3) + if (version != 4) { + XPIS[name] = xpi; + } + + // update_url above points to a test manifest that references the next + // version. The xpi is made available on the server, so that the test + // can verify that the blocklist works as intended (i.e. update to newer + // version is blocked). + // There is nothing that updates to version 1, only to versions 2, 3, 4. + if (version != 1) { + testserver.registerFile(`/addons/${name}.xpi`, xpi); + } + } + } + + // For each version that this test file uses, create a test manifest that + // references the next version for each id in ADDON_IDS. + for (let version of [1, 2, 3]) { + let updateJson = { addons: {} }; + for (let id of ADDON_IDS) { + let nextversion = version + 1; + let name = getxpibasename(id, nextversion); + updateJson.addons[id] = { + updates: [ + { + applications: { + gecko: { + strict_min_version: "0", + advisory_max_version: "*", + }, + }, + version: `${nextversion}.0`, + update_link: `http://example.com/addons/${name}.xpi`, + }, + ], + }; + } + AddonTestUtils.registerJSON( + testserver, + `/addon_update${version}.json`, + updateJson + ); + } + + await promiseStartupManager(); + + await promiseInstallFile(XPIS.blocklist_soft1_1); + await promiseInstallFile(XPIS.blocklist_soft2_1); + await promiseInstallFile(XPIS.blocklist_soft3_1); + await promiseInstallFile(XPIS.blocklist_soft4_1); + await promiseInstallFile(XPIS.blocklist_hard_1); + await promiseInstallFile(XPIS.blocklist_regexp_1); + + let s4 = await promiseAddonByID("softblock4@tests.mozilla.org"); + await s4.disable(); +}); + +// Starts with add-ons unblocked and then switches application versions to +// change add-ons to blocked and back +add_task(async function run_app_update_test() { + await Pload_blocklist("app_update"); + await promiseRestartManager(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s2, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); +}); + +add_task(async function app_update_step_2() { + await promiseRestartManagerWithAppChange("2"); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await s2.enable(); + await s2.disable(); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + await s3.enable(); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); +}); + +add_task(async function app_update_step_3() { + await promiseRestartManager(); + + await promiseRestartManagerWithAppChange("2.5"); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); +}); + +add_task(async function app_update_step_4() { + await promiseRestartManagerWithAppChange("1"); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await s1.enable(); + await s2.enable(); +}); + +// Starts with add-ons unblocked and then switches application versions to +// change add-ons to blocked and back. A DB schema change is faked to force a +// rebuild when the application version changes +add_task(async function run_app_update_schema_test() { + await promiseRestartManager(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s2, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); +}); + +add_task(async function update_schema_2() { + await promiseShutdownManager(); + + await changeXPIDBVersion(100); + gAppInfo.version = "2"; + await promiseStartupManagerWithAppChange(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await s2.enable(); + await s2.disable(); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + await s3.enable(); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); +}); + +add_task(async function update_schema_3() { + await promiseRestartManager(); + + await promiseShutdownManager(); + await changeXPIDBVersion(100); + gAppInfo.version = "2.5"; + await promiseStartupManagerWithAppChange(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); +}); + +add_task(async function update_schema_4() { + await promiseShutdownManager(); + + await changeXPIDBVersion(100); + await promiseStartupManager(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); +}); + +add_task(async function update_schema_5() { + await promiseShutdownManager(); + + await changeXPIDBVersion(100); + gAppInfo.version = "1"; + await promiseStartupManagerWithAppChange(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await s1.enable(); + await s2.enable(); +}); + +// Starts with add-ons unblocked and then loads new blocklists to change add-ons +// to blocked and back again. +add_task(async function run_blocklist_update_test() { + await Pload_blocklist("empty_blocklist"); + await promiseRestartManager(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s2, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await Pload_blocklist("blocklist_update2"); + await promiseRestartManager(); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await s2.enable(); + await s2.disable(); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + await s3.enable(); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + + await promiseRestartManager(); + + await Pload_blocklist("blocklist_update2"); + await promiseRestartManager(); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await Pload_blocklist("empty_blocklist"); + await promiseRestartManager(); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await s1.enable(); + await s2.enable(); +}); + +// Starts with add-ons unblocked and then new versions are installed outside of +// the app to change them to blocked and back again. +add_task(async function run_addon_change_test() { + await Pload_blocklist("addon_change"); + await promiseRestartManager(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s2, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); +}); + +add_task(async function run_addon_change_2() { + await promiseInstallFile(XPIS.blocklist_soft1_2); + await promiseInstallFile(XPIS.blocklist_soft2_2); + await promiseInstallFile(XPIS.blocklist_soft3_2); + await promiseInstallFile(XPIS.blocklist_soft4_2); + await promiseInstallFile(XPIS.blocklist_hard_2); + await promiseInstallFile(XPIS.blocklist_regexp_2); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s4, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "2.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "2.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await s2.enable(); + await s2.disable(); + check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + await s3.enable(); + check_addon( + s3, + "2.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); +}); + +add_task(async function run_addon_change_3() { + await promiseInstallFile(XPIS.blocklist_soft1_3); + await promiseInstallFile(XPIS.blocklist_soft2_3); + await promiseInstallFile(XPIS.blocklist_soft3_3); + await promiseInstallFile(XPIS.blocklist_soft4_3); + await promiseInstallFile(XPIS.blocklist_hard_3); + await promiseInstallFile(XPIS.blocklist_regexp_3); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon( + s3, + "3.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + check_addon(s4, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); +}); + +add_task(async function run_addon_change_4() { + await promiseInstallFile(XPIS.blocklist_soft1_1); + await promiseInstallFile(XPIS.blocklist_soft2_1); + await promiseInstallFile(XPIS.blocklist_soft3_1); + await promiseInstallFile(XPIS.blocklist_soft4_1); + await promiseInstallFile(XPIS.blocklist_hard_1); + await promiseInstallFile(XPIS.blocklist_regexp_1); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await s1.enable(); + await s2.enable(); +}); + +// Add-ons are initially unblocked then attempts to upgrade to blocked versions +// in the background which should fail +add_task(async function run_background_update_test() { + await promiseRestartManager(); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s2, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await Pbackground_update(); + await promiseRestartManager(); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s2, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); +}); + +// Starts with add-ons blocked and then new versions are detected and installed +// automatically for unblocked versions. +add_task(async function run_background_update_2_test() { + await promiseInstallFile(XPIS.blocklist_soft1_3); + await promiseInstallFile(XPIS.blocklist_soft2_3); + await promiseInstallFile(XPIS.blocklist_soft3_3); + await promiseInstallFile(XPIS.blocklist_soft4_3); + await promiseInstallFile(XPIS.blocklist_hard_3); + await promiseInstallFile(XPIS.blocklist_regexp_3); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await s2.enable(); + await s2.disable(); + check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + await s3.enable(); + check_addon( + s3, + "3.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + + await Pbackground_update(); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "4.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s2, "4.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon( + s3, + "4.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(h, "4.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "4.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await s1.enable(); + await s2.enable(); + await s4.disable(); +}); + +// The next test task (run_manual_update_test) was written to expect version 1, +// but after the previous test, version 4 of the add-ons were installed. +add_task(async function reset_addons_to_version_1_instead_of_4() { + await promiseInstallFile(XPIS.blocklist_soft1_1); + await promiseInstallFile(XPIS.blocklist_soft2_1); + await promiseInstallFile(XPIS.blocklist_soft3_1); + await promiseInstallFile(XPIS.blocklist_soft4_1); + await promiseInstallFile(XPIS.blocklist_hard_1); + await promiseInstallFile(XPIS.blocklist_regexp_1); +}); + +// Starts with add-ons blocked and then simulates the user upgrading them to +// unblocked versions. +add_task(async function run_manual_update_test() { + await Pload_blocklist("manual_update"); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await s2.enable(); + await s2.disable(); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + await s3.enable(); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + + await Pmanual_update("2"); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + // With useMLBF, s1/s2/s3 are hard blocks, so they cannot update. + const sv2 = useMLBF ? "1.0" : "2.0"; + check_addon(s1, sv2, true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, sv2, true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, sv2, false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s4, sv2, true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + // Can't manually update to a hardblocked add-on + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await Pmanual_update("3"); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "3.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon( + s3, + "3.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s4, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); +}); + +// Starts with add-ons blocked and then new versions are installed outside of +// the app to change them to unblocked. +add_task(async function run_manual_update_2_test() { + let addons = await promiseAddonsByIDs(ADDON_IDS); + await Promise.all(addons.map(addon => addon.uninstall())); + + await promiseInstallFile(XPIS.blocklist_soft1_1); + await promiseInstallFile(XPIS.blocklist_soft2_1); + await promiseInstallFile(XPIS.blocklist_soft3_1); + await promiseInstallFile(XPIS.blocklist_soft4_1); + await promiseInstallFile(XPIS.blocklist_hard_1); + await promiseInstallFile(XPIS.blocklist_regexp_1); + + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await s2.enable(); + await s2.disable(); + check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + await s3.enable(); + check_addon( + s3, + "1.0", + false, + false, + Ci.nsIBlocklistService.STATE_SOFTBLOCKED + ); + + await Pmanual_update("2"); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + // With useMLBF, s1/s2/s3 are hard blocks, so they cannot update. + const sv2 = useMLBF ? "1.0" : "2.0"; + check_addon(s1, sv2, true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, sv2, true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, sv2, false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + // Can't manually update to a hardblocked add-on + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + + await Pmanual_update("3"); + + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon( + s1, + "3.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon( + s3, + "3.0", + false, + false, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); + + await s1.enable(); + await s2.enable(); + await s4.disable(); +}); + +// Uses the API to install blocked add-ons from the local filesystem +add_task(async function run_local_install_test() { + let addons = await promiseAddonsByIDs(ADDON_IDS); + await Promise.all(addons.map(addon => addon.uninstall())); + + await promiseInstallAllFiles([ + XPIS.blocklist_soft1_1, + XPIS.blocklist_soft2_1, + XPIS.blocklist_soft3_1, + XPIS.blocklist_soft4_1, + XPIS.blocklist_hard_1, + XPIS.blocklist_regexp_1, + ]); + + let installs = await AddonManager.getAllInstalls(); + // Should have finished all installs without needing to restart + Assert.equal(installs.length, 0); + + let [s1, s2, s3 /* s4 */, , h, r] = await promiseAddonsByIDs(ADDON_IDS); + + check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); + check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); + check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js new file mode 100644 index 0000000000..d884438def --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// useMLBF=true doesn't support soft blocks, regexps or version ranges. +// Flip the useMLBF preference to make sure that the test_blocklistchange.js +// test works with and without this pref (blocklist v2 and blocklist v3). +enable_blocklist_v2_instead_of_useMLBF(); + +Services.scriptloader.loadSubScript( + Services.io.newFileURI(do_get_file("test_blocklistchange.js")).spec, + this +); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js new file mode 100644 index 0000000000..9b1d84b77d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which differs only on device ID, but otherwise +// exactly matches the blacklist entry, is not blocked. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x9876"); + gfxInfo.spoofDriverVersion("8.52.322.2201"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x9876"); + break; + case "Darwin": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x9876"); + gfxInfo.spoofOSVersion(0xa0900); + break; + case "Android": + gfxInfo.spoofVendorID("abcd"); + gfxInfo.spoofDeviceID("aabb"); + gfxInfo.spoofDriverVersion("5"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_CANVAS2D_ACCELERATION + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js new file mode 100644 index 0000000000..a1bcde5566 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a new-enough driver bypasses the blacklist, even if the rest of +// the attributes match the blacklist entry. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2202"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + // We don't support driver versions on Linux. + do_test_finished(); + return; + case "Darwin": + // We don't support driver versions on Darwin. + do_test_finished(); + return; + case "Android": + gfxInfo.spoofVendorID("abcd"); + gfxInfo.spoofDeviceID("wxyz"); + gfxInfo.spoofDriverVersion("6"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js new file mode 100644 index 0000000000..ec74d813ae --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which is newer than the equal +// blacklist entry is allowed. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xdcdc"); + gfxInfo.spoofDeviceID("0x1234"); + // test_gfxBlacklist.json has several entries targeting "os": "All" + // ("All" meaning "All Windows"), with several combinations of + // "driverVersion" / "driverVersionMax" / "driverVersionComparator". + gfxInfo.spoofDriverVersion("8.52.322.1112"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + // We don't support driver versions on Linux. + // XXX don't we? Seems like we do since bug 1294232 with the change in + // https://hg.mozilla.org/mozilla-central/diff/8962b8d9b7a6/widget/GfxInfoBase.cpp + // To update this test, we'd have to update test_gfxBlacklist.json in a + // way similar to how bug 1714673 was resolved for Android. + do_test_finished(); + return; + case "Darwin": + // We don't support driver versions on OS X. + do_test_finished(); + return; + case "Android": + gfxInfo.spoofVendorID("dcdc"); + gfxInfo.spoofDeviceID("uiop"); + gfxInfo.spoofDriverVersion("6"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "15.0", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_ANGLE); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_HARDWARE_VIDEO_DECODING + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_H264 + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_DECODE + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_ENCODE + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_CANVAS2D_ACCELERATION + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js new file mode 100644 index 0000000000..ff887a92eb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which is older than the equal +// blacklist entry is correctly allowed. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xdcdc"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.1110"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + // We don't support driver versions on Linux. + do_test_finished(); + return; + case "Darwin": + // We don't support driver versions on Darwin. + do_test_finished(); + return; + case "Android": + gfxInfo.spoofVendorID("dcdc"); + gfxInfo.spoofDeviceID("uiop"); + gfxInfo.spoofDriverVersion("4"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js new file mode 100644 index 0000000000..1eef119663 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which exactly matches the equal +// blacklist entry is successfully blocked. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xdcdc"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.1111"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + // We don't support driver versions on Linux. + do_test_finished(); + return; + case "Darwin": + // We don't support driver versions on Darwin. + do_test_finished(); + return; + case "Android": + gfxInfo.spoofVendorID("dcdc"); + gfxInfo.spoofDeviceID("uiop"); + gfxInfo.spoofDriverVersion("5"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js new file mode 100644 index 0000000000..182c825ffb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which is lower than the greater-than-or-equal +// blacklist entry is allowed. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabab"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2201"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + // We don't support driver versions on Linux. + do_test_finished(); + return; + case "Darwin": + // We don't support driver versions on Darwin. + do_test_finished(); + return; + case "Android": + gfxInfo.spoofVendorID("abab"); + gfxInfo.spoofDeviceID("ghjk"); + gfxInfo.spoofDriverVersion("6"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js new file mode 100644 index 0000000000..2cc3686007 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which exactly matches the greater-than-or-equal +// blacklist entry is successfully blocked. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabab"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2202"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + // We don't support driver versions on Linux. + // XXX don't we? Seems like we do since bug 1294232 with the change in + // https://hg.mozilla.org/mozilla-central/diff/8962b8d9b7a6/widget/GfxInfoBase.cpp + do_test_finished(); + return; + case "Darwin": + // We don't support driver versions on Darwin. + do_test_finished(); + return; + case "Android": + gfxInfo.spoofVendorID("abab"); + gfxInfo.spoofDeviceID("ghjk"); + gfxInfo.spoofDriverVersion("7"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js new file mode 100644 index 0000000000..169cdc5e62 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which exactly matches the blacklist entry is +// successfully blocked. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x6666"); + + // Spoof the OS version so it matches the test file. + switch (Services.appinfo.OS) { + case "WINNT": + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + break; + case "Darwin": + gfxInfo.spoofOSVersion(0xa0900); + break; + case "Android": + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var driverVersion = gfxInfo.adapterDriverVersion; + if (driverVersion) { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBRENDER); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js new file mode 100644 index 0000000000..04d766e027 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which exactly matches the blacklist entry is +// successfully blocked. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2201"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + break; + case "Darwin": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofOSVersion(0xa0900); + break; + case "Android": + gfxInfo.spoofVendorID("abcd"); + gfxInfo.spoofDeviceID("asdf"); + gfxInfo.spoofDriverVersion("5"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js new file mode 100644 index 0000000000..ce5a61cb75 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which differs only on OS version, but otherwise +// exactly matches the blacklist entry, is not blocked. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2201"); + // Windows Vista + gfxInfo.spoofOSVersion(0x60000); + break; + case "Linux": + // We don't have any OS versions on Linux, just "Linux". + do_test_finished(); + return; + case "Darwin": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofOSVersion(0xa0800); + break; + case "Android": + // On Android, the driver version is used as the OS version (because + // there's so many of them). + do_test_finished(); + return; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js new file mode 100644 index 0000000000..7a4ec276ee --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether new OS versions are matched properly. +// Uses test_gfxBlacklist_OSVersion.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + gfxInfo.spoofDriverVersion("8.52.322.2201"); + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + + // Spoof the version of the OS appropriately to test the test file. + switch (Services.appinfo.OS) { + case "WINNT": + // Windows 8 + gfxInfo.spoofOSVersion(0x60002); + break; + case "Linux": + // We don't have any OS versions on Linux, just "Linux". + do_test_finished(); + return; + case "Darwin": + // Mountain Lion + gfxInfo.spoofOSVersion(0xa0900); + break; + case "Android": + // On Android, the driver version is used as the OS version (because + // there's so many of them). + do_test_finished(); + return; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + if (Services.appinfo.OS == "WINNT") { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + } else if (Services.appinfo.OS == "Darwin") { + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + } + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_OSVersion.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js new file mode 100644 index 0000000000..61dba8db96 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether blocklists specifying new OSes correctly don't block if driver +// versions are appropriately up-to-date. +// Uses test_gfxBlacklist_OSVersion.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + gfxInfo.spoofDriverVersion("8.52.322.2202"); + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + + // Spoof the version of the OS appropriately to test the test file. + switch (Services.appinfo.OS) { + case "WINNT": + // Windows 8 + gfxInfo.spoofOSVersion(0x60002); + break; + case "Linux": + // We don't have any OS versions on Linux, just "Linux". + do_test_finished(); + return; + case "Darwin": + gfxInfo.spoofOSVersion(0xa0800); + break; + case "Android": + // On Android, the driver version is used as the OS version (because + // there's so many of them). + do_test_finished(); + return; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + if (Services.appinfo.OS == "WINNT") { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } else if (Services.appinfo.OS == "Darwin") { + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_OSVersion.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js new file mode 100644 index 0000000000..117e2a34ee --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether old OS versions are not matched when the blacklist contains +// only new OS versions. +// Uses test_gfxBlacklist_OSVersion.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + gfxInfo.spoofDriverVersion("8.52.322.2201"); + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + + // Spoof the version of the OS appropriately to test the test file. + switch (Services.appinfo.OS) { + case "WINNT": + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + // We don't have any OS versions on Linux, just "Linux". + do_test_finished(); + return; + case "Darwin": + // Lion + gfxInfo.spoofOSVersion(0xa0800); + break; + case "Android": + // On Android, the driver version is used as the OS version (because + // there's so many of them). + do_test_finished(); + return; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + if (Services.appinfo.OS == "WINNT") { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } else if (Services.appinfo.OS == "Darwin") { + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_OSVersion.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js new file mode 100644 index 0000000000..37bc0d3c89 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which differs only on vendor, but otherwise +// exactly matches the blacklist entry, is not blocked. +// Uses test_gfxBlacklist.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xdcba"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2201"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + gfxInfo.spoofVendorID("0xdcba"); + gfxInfo.spoofDeviceID("0x1234"); + break; + case "Darwin": + gfxInfo.spoofVendorID("0xdcba"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofOSVersion(0xa0900); + break; + case "Android": + gfxInfo.spoofVendorID("dcba"); + gfxInfo.spoofDeviceID("asdf"); + gfxInfo.spoofDriverVersion("5"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function checkBlacklist() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlacklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js new file mode 100644 index 0000000000..9a6a904465 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js @@ -0,0 +1,190 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether a machine which exactly matches the blocklist entry is +// successfully blocked. +// Uses test_gfxBlacklist_AllOS.json + +// Performs the initial setup +async function run_test() { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Save OS in variable since createAppInfo below will change it to "xpcshell". + const OS = Services.appinfo.OS; + // Set the vendor/device ID, etc, to match the test file. + switch (OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2201"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + break; + case "Darwin": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofOSVersion(0xa0900); + break; + case "Android": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("5"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "15.0", "8"); + await promiseStartupManager(); + + function checkBlocklist() { + var failureId = {}; + var status; + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DIRECT2D, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_g1"); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_g2"); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DIRECT3D_10_LAYERS, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + Assert.equal(failureId.value, ""); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DIRECT3D_10_1_LAYERS, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + Assert.equal(failureId.value, ""); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_g11"); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_NO_ID"); + + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL2, failureId); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_NO_ID"); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_STAGEFRIGHT, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_H264, + failureId + ); + if (OS == "Android" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) { + // Hardware acceleration for H.264 varies by device. + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE); + Assert.equal(failureId.value, "FEATURE_FAILURE_WEBRTC_H264"); + } else { + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, + failureId + ); + if (OS == "Android" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) { + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE); + Assert.equal(failureId.value, "FEATURE_FAILURE_WEBRTC_ENCODE"); + } else { + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_DECODE, + failureId + ); + if (OS == "Android" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) { + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE); + Assert.equal(failureId.value, "FEATURE_FAILURE_WEBRTC_DECODE"); + } else { + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_LAYERS, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_HARDWARE_VIDEO_DECODING, + failureId + ); + if (OS == "Linux" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) { + // Linux test suite is running on SW OpenGL backend and we disable + // HW video decoding there. + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_PLATFORM_TEST); + Assert.equal( + failureId.value, + "FEATURE_FAILURE_VIDEO_DECODING_TEST_FAILED" + ); + } else { + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + } + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_ANGLE, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + status = gfxInfo.getFeatureStatus( + Ci.nsIGfxInfo.FEATURE_DX_INTEROP2, + failureId + ); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + do_test_finished(); + } + + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(checkBlocklist); + }, "blocklist-data-gfxItems"); + + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_AllOS.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js new file mode 100644 index 0000000000..34e92b0e80 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js @@ -0,0 +1,124 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test whether the blacklist successfully adds and removes the prefs that store +// its decisions when the remote blacklist is changed. +// Uses test_gfxBlacklist.json and test_gfxBlacklist2.json + +// Performs the initial setup +async function run_test() { + try { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + } catch (e) { + do_test_finished(); + return; + } + + // We can't do anything if we can't spoof the stuff we need. + if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) { + do_test_finished(); + return; + } + + gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug); + + // Set the vendor/device ID, etc, to match the test file. + switch (Services.appinfo.OS) { + case "WINNT": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofDriverVersion("8.52.322.2201"); + // Windows 7 + gfxInfo.spoofOSVersion(0x60001); + break; + case "Linux": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + break; + case "Darwin": + gfxInfo.spoofVendorID("0xabcd"); + gfxInfo.spoofDeviceID("0x1234"); + gfxInfo.spoofOSVersion(0xa0900); + break; + case "Android": + gfxInfo.spoofVendorID("abcd"); + gfxInfo.spoofDeviceID("asdf"); + gfxInfo.spoofDriverVersion("5"); + break; + } + + do_test_pending(); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8"); + await promiseStartupManager(); + + function blacklistAdded(aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(ensureBlacklistSet); + } + function ensureBlacklistSet() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + Assert.equal( + Services.prefs.getIntPref("gfx.blacklist.direct2d"), + Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION + ); + + Services.obs.removeObserver(blacklistAdded, "blocklist-data-gfxItems"); + Services.obs.addObserver(blacklistRemoved, "blocklist-data-gfxItems"); + mockGfxBlocklistItems([ + { + os: "WINNT 6.1", + vendor: "0xabcd", + devices: ["0x2783", "0x2782"], + feature: " DIRECT2D ", + featureStatus: " BLOCKED_DRIVER_VERSION ", + driverVersion: " 8.52.322.2202 ", + driverVersionComparator: " LESS_THAN ", + }, + { + os: "WINNT 6.0", + vendor: "0xdcba", + devices: ["0x2783", "0x1234", "0x2782"], + feature: " DIRECT3D_9_LAYERS ", + featureStatus: " BLOCKED_DRIVER_VERSION ", + driverVersion: " 8.52.322.2202 ", + driverVersionComparator: " LESS_THAN ", + }, + ]); + } + + function blacklistRemoved(aSubject, aTopic, aData) { + // If we wait until after we go through the event loop, gfxInfo is sure to + // have processed the gfxItems event. + executeSoon(ensureBlacklistUnset); + } + function ensureBlacklistUnset() { + var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + // Make sure unrelated features aren't affected + status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS); + Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK); + + var exists = false; + try { + Services.prefs.getIntPref("gfx.blacklist.direct2d"); + exists = true; + } catch (e) {} + + Assert.ok(!exists); + + do_test_finished(); + } + + Services.obs.addObserver(blacklistAdded, "blocklist-data-gfxItems"); + mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json"); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js new file mode 100644 index 0000000000..edf53183d0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// useMLBF=true only supports hard blocks, not soft blocks. +enable_blocklist_v2_instead_of_useMLBF(); + +// Tests that an appDisabled add-on that becomes softBlocked remains disabled +// when becoming appEnabled +add_task(async function test_softblock() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + await promiseStartupManager(); + + await promiseInstallWebExtension({ + manifest: { + name: "Softblocked add-on", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "softblock1@tests.mozilla.org", + strict_min_version: "2", + strict_max_version: "3", + }, + }, + }, + }); + let s1 = await promiseAddonByID("softblock1@tests.mozilla.org"); + + // Make sure to mark it as previously enabled. + await s1.enable(); + + Assert.ok(!s1.softDisabled); + Assert.ok(s1.appDisabled); + Assert.ok(!s1.isActive); + + await AddonTestUtils.loadBlocklistRawData({ + extensions: [ + { + guid: "softblock1@tests.mozilla.org", + versionRange: [ + { + severity: "1", + }, + ], + }, + ], + }); + + Assert.ok(s1.softDisabled); + Assert.ok(s1.appDisabled); + Assert.ok(!s1.isActive); + + AddonTestUtils.appInfo.platformVersion = "2"; + await promiseRestartManager("2"); + + s1 = await promiseAddonByID("softblock1@tests.mozilla.org"); + + Assert.ok(s1.softDisabled); + Assert.ok(!s1.appDisabled); + Assert.ok(!s1.isActive); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini new file mode 100644 index 0000000000..f13a7645c2 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini @@ -0,0 +1,67 @@ +[DEFAULT] +tags = addons blocklist +head = head.js ../head_addons.js +firefox-appdir = browser +support-files = + ../data/** + ../../xpinstall/webmidi_permission.xpi + +[test_android_blocklist_dump.js] +run-if = os == "android" +[test_blocklist_addonBlockURL.js] +[test_blocklist_appversion.js] +skip-if = os == "android" && verify # times out +[test_blocklist_clients.js] +tags = remote-settings +[test_blocklist_gfx.js] +[test_blocklist_metadata_filters.js] +[test_blocklist_mlbf.js] +[test_blocklist_mlbf_dump.js] +skip-if = os == "android" # blocklist v3 is not bundled with Android builds, see test_android_blocklist_dump.js instead. +[test_blocklist_mlbf_fetch.js] +[test_blocklist_mlbf_stashes.js] +[test_blocklist_mlbf_telemetry.js] +skip-if = + appname == "thunderbird" # Data irrelevant to Thunderbird. Bug 1641400. +[test_blocklist_mlbf_update.js] +[test_blocklist_osabi.js] +skip-if = os == "android" && verify # times out +[test_blocklist_prefs.js] +[test_blocklist_regexp_split.js] +[test_blocklist_severities.js] +[test_blocklist_statechange_telemetry.js] +skip-if = + appname == "thunderbird" # Data irrelevant to Thunderbird. Bug 1641400. +[test_blocklist_targetapp_filter.js] +tags = remote-settings +[test_blocklist_telemetry.js] +tags = remote-settings +skip-if = + appname == "thunderbird" # Data irrelevant to Thunderbird. Bug 1641400. +[test_blocklistchange.js] +# Times out during parallel runs on desktop +requesttimeoutfactor = 2 +skip-if = os == "android" && verify # times out because it takes too much time to run the full test +[test_blocklistchange_v2.js] +# Times out during parallel runs on desktop +requesttimeoutfactor = 2 +skip-if = os == "android" && verify # times out in chaos mode on Android because several minutes are spent waiting at https://hg.mozilla.org/mozilla-central/file/3350b680/toolkit/mozapps/extensions/Blocklist.jsm#l698 +[test_gfxBlacklist_Device.js] +[test_gfxBlacklist_DriverNew.js] +[test_gfxBlacklist_Equal_DriverNew.js] +[test_gfxBlacklist_Equal_DriverOld.js] +[test_gfxBlacklist_Equal_OK.js] +[test_gfxBlacklist_GTE_DriverOld.js] +[test_gfxBlacklist_GTE_OK.js] +[test_gfxBlacklist_No_Comparison.js] +[test_gfxBlacklist_OK.js] +[test_gfxBlacklist_OS.js] +[test_gfxBlacklist_OSVersion_match.js] +[test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js] +[test_gfxBlacklist_OSVersion_mismatch_OSVersion.js] +[test_gfxBlacklist_Vendor.js] +[test_gfxBlacklist_Version.js] +[test_gfxBlacklist_prefs.js] +# Bug 1248787 - consistently fails +skip-if = true +[test_softblocked.js] diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js b/toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js new file mode 100644 index 0000000000..23420bd911 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js @@ -0,0 +1,904 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const { AbuseReporter, AbuseReportError } = ChromeUtils.importESModule( + "resource://gre/modules/AbuseReporter.sys.mjs" +); + +const { ClientID } = ChromeUtils.importESModule( + "resource://gre/modules/ClientID.sys.mjs" +); +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const APPNAME = "XPCShell"; +const APPVERSION = "1"; +const ADDON_ID = "test-addon@tests.mozilla.org"; +const ADDON_ID2 = "test-addon2@tests.mozilla.org"; +const FAKE_INSTALL_INFO = { + source: "fake-Install:Source", + method: "fake:install method", +}; +const PREF_REQUIRED_LOCALE = "intl.locale.requested"; +const REPORT_OPTIONS = { reportEntryPoint: "menu" }; +const TELEMETRY_EVENTS_FILTERS = { + category: "addonsManager", + method: "report", +}; + +const FAKE_AMO_DETAILS = { + name: { + "en-US": "fake name", + "it-IT": "fake it-IT name", + }, + current_version: { version: "1.0" }, + type: "extension", + is_recommended: true, +}; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49"); + +const server = createHttpServer({ hosts: ["test.addons.org"] }); + +// Mock abuse report API endpoint. +let apiRequestHandler; +server.registerPathHandler("/api/report/", (request, response) => { + const stream = request.bodyInputStream; + const buffer = NetUtil.readInputStream(stream, stream.available()); + const data = new TextDecoder().decode(buffer); + apiRequestHandler({ data, request, response }); +}); + +// Mock addon details API endpoint. +const amoAddonDetailsMap = new Map(); +server.registerPrefixHandler("/api/addons/addon/", (request, response) => { + const addonId = request.path.split("/").pop(); + if (!amoAddonDetailsMap.has(addonId)) { + response.setStatusLine(request.httpVersion, 404, "Not Found"); + response.write(JSON.stringify({ detail: "Not found." })); + } else { + response.setStatusLine(request.httpVersion, 200, "Success"); + response.write(JSON.stringify(amoAddonDetailsMap.get(addonId))); + } +}); + +function getProperties(obj, propNames) { + return propNames.reduce((acc, el) => { + acc[el] = obj[el]; + return acc; + }, {}); +} + +function handleSubmitRequest({ request, response }) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json", false); + response.write("{}"); +} + +function clearAbuseReportState() { + // Clear the timestamp of the last submission. + AbuseReporter._lastReportTimestamp = null; +} + +async function installTestExtension(overrideOptions = {}) { + const extOptions = { + manifest: { + browser_specific_settings: { gecko: { id: ADDON_ID } }, + name: "Test Extension", + }, + useAddonManager: "permanent", + amInstallTelemetryInfo: FAKE_INSTALL_INFO, + ...overrideOptions, + }; + + const extension = ExtensionTestUtils.loadExtension(extOptions); + await extension.startup(); + + const addon = await AddonManager.getAddonByID(ADDON_ID); + + return { extension, addon }; +} + +async function assertRejectsAbuseReportError(promise, errorType, errorInfo) { + let error; + + await Assert.rejects( + promise, + err => { + // Log the actual error to make investigating test failures easier. + Cu.reportError(err); + error = err; + return err instanceof AbuseReportError; + }, + `Got an AbuseReportError` + ); + + equal(error.errorType, errorType, "Got the expected errorType"); + equal(error.errorInfo, errorInfo, "Got the expected errorInfo"); + ok( + error.message.includes(errorType), + "errorType should be included in the error message" + ); + if (errorInfo) { + ok( + error.message.includes(errorInfo), + "errorInfo should be included in the error message" + ); + } +} + +async function assertBaseReportData({ reportData, addon }) { + // Report properties related to addon metadata. + equal(reportData.addon, ADDON_ID, "Got expected 'addon'"); + equal( + reportData.addon_version, + addon.version, + "Got expected 'addon_version'" + ); + equal( + reportData.install_date, + addon.installDate.toISOString(), + "Got expected 'install_date' in ISO format" + ); + equal( + reportData.addon_install_origin, + addon.sourceURI.spec, + "Got expected 'addon_install_origin'" + ); + equal( + reportData.addon_install_source, + "fake_install_source", + "Got expected 'addon_install_source'" + ); + equal( + reportData.addon_install_method, + "fake_install_method", + "Got expected 'addon_install_method'" + ); + equal( + reportData.addon_signature, + "privileged", + "Got expected 'addon_signature'" + ); + + // Report properties related to the environment. + equal( + reportData.client_id, + await ClientID.getClientIdHash(), + "Got the expected 'client_id'" + ); + equal(reportData.app, APPNAME.toLowerCase(), "Got expected 'app'"); + equal(reportData.appversion, APPVERSION, "Got expected 'appversion'"); + equal( + reportData.lang, + Services.locale.appLocaleAsBCP47, + "Got expected 'lang'" + ); + equal( + reportData.operating_system, + AppConstants.platform, + "Got expected 'operating_system'" + ); + equal( + reportData.operating_system_version, + Services.sysinfo.getProperty("version"), + "Got expected 'operating_system_version'" + ); +} + +add_task(async function test_setup() { + Services.prefs.setCharPref( + "extensions.abuseReport.url", + "http://test.addons.org/api/report/" + ); + + Services.prefs.setCharPref( + "extensions.abuseReport.amoDetailsURL", + "http://test.addons.org/api/addons/addon" + ); + + await promiseStartupManager(); + // Telemetry test setup needed to ensure that the builtin events are defined + // and they can be collected and verified. + await TelemetryController.testSetup(); + + // This is actually only needed on Android, because it does not properly support unified telemetry + // and so, if not enabled explicitly here, it would make these tests to fail when running on a + // non-Nightly build. + const oldCanRecordBase = Services.telemetry.canRecordBase; + Services.telemetry.canRecordBase = true; + registerCleanupFunction(() => { + Services.telemetry.canRecordBase = oldCanRecordBase; + }); + + // Register a fake it-IT locale (used to test localized AMO details in some + // of the test case defined in this test file). + L10nRegistry.getInstance().registerSources([ + L10nFileSource.createMock( + "mock", + "app", + ["it-IT", "fr-FR"], + "resource://fake/locales/{locale}", + [] + ), + ]); +}); + +add_task(async function test_addon_report_data() { + info("Verify report property for a privileged extension"); + const { addon, extension } = await installTestExtension(); + const data = await AbuseReporter.getReportData(addon); + await assertBaseReportData({ reportData: data, addon }); + await extension.unload(); + + info("Verify 'addon_signature' report property for non privileged extension"); + AddonTestUtils.usePrivilegedSignatures = false; + const { addon: addon2, extension: extension2 } = await installTestExtension(); + const data2 = await AbuseReporter.getReportData(addon2); + equal( + data2.addon_signature, + "signed", + "Got expected 'addon_signature' for non privileged extension" + ); + await extension2.unload(); + + info("Verify 'addon_install_method' report property on temporary install"); + const { addon: addon3, extension: extension3 } = await installTestExtension({ + useAddonManager: "temporary", + }); + const data3 = await AbuseReporter.getReportData(addon3); + equal( + data3.addon_install_source, + "temporary_addon", + "Got expected 'addon_install_method' on temporary install" + ); + await extension3.unload(); +}); + +add_task(async function test_report_on_not_installed_addon() { + Services.telemetry.clearEvents(); + + // Make sure that the AMO addons details API endpoint is going to + // return a 404 status for the not installed addon. + amoAddonDetailsMap.delete(ADDON_ID); + + await assertRejectsAbuseReportError( + AbuseReporter.createAbuseReport(ADDON_ID, REPORT_OPTIONS), + "ERROR_ADDON_NOTFOUND" + ); + + TelemetryTestUtils.assertEvents( + [ + { + object: REPORT_OPTIONS.reportEntryPoint, + value: ADDON_ID, + extra: { error_type: "ERROR_AMODETAILS_NOTFOUND" }, + }, + { + object: REPORT_OPTIONS.reportEntryPoint, + value: ADDON_ID, + extra: { error_type: "ERROR_ADDON_NOTFOUND" }, + }, + ], + TELEMETRY_EVENTS_FILTERS + ); + + Services.telemetry.clearEvents(); +}); + +// This tests verifies how the addon installTelemetryInfo values are being +// normalized into the addon_install_source and addon_install_method +// expected by the API endpoint. +add_task(async function test_normalized_addon_install_source_and_method() { + async function assertAddonInstallMethod(amInstallTelemetryInfo, expected) { + const { addon, extension } = await installTestExtension({ + amInstallTelemetryInfo, + }); + const { + addon_install_method, + addon_install_source, + addon_install_source_url, + } = await AbuseReporter.getReportData(addon); + + Assert.deepEqual( + { + addon_install_method, + addon_install_source, + addon_install_source_url, + }, + { + addon_install_method: expected.method, + addon_install_source: expected.source, + addon_install_source_url: expected.sourceURL, + }, + `Got the expected report data for ${JSON.stringify( + amInstallTelemetryInfo + )}` + ); + await extension.unload(); + } + + // Array of testcases: the `test` property contains the installTelemetryInfo value + // and the `expect` contains the expected normalized values. + const TEST_CASES = [ + // Explicitly verify normalized values on missing telemetry info. + { + test: null, + expect: { source: null, method: null }, + }, + + // Verify expected normalized values for some common install telemetry info. + { + test: { source: "about:addons", method: "drag-and-drop" }, + expect: { source: "about_addons", method: "drag_and_drop" }, + }, + { + test: { source: "amo", method: "amWebAPI" }, + expect: { source: "amo", method: "amwebapi" }, + }, + { + test: { source: "app-profile", method: "sideload" }, + expect: { source: "app_profile", method: "sideload" }, + }, + { + test: { source: "distribution" }, + expect: { source: "distribution", method: null }, + }, + { + test: { + method: "installTrigger", + source: "test-host", + sourceURL: "http://host.triggered.install/example?test=1", + }, + expect: { + method: "installtrigger", + source: "test_host", + sourceURL: "http://host.triggered.install/example?test=1", + }, + }, + { + test: { + method: "link", + source: "unknown", + sourceURL: "https://another.host/installExtension?name=ext1", + }, + expect: { + method: "link", + source: "unknown", + sourceURL: "https://another.host/installExtension?name=ext1", + }, + }, + ]; + + for (const { expect, test } of TEST_CASES) { + await assertAddonInstallMethod(test, expect); + } +}); + +add_task(async function test_report_create_and_submit() { + Services.telemetry.clearEvents(); + + // Override the test api server request handler, to be able to + // intercept the submittions to the test api server. + let reportSubmitted; + apiRequestHandler = ({ data, request, response }) => { + reportSubmitted = JSON.parse(data); + handleSubmitRequest({ request, response }); + }; + + const { addon, extension } = await installTestExtension(); + + const reportEntryPoint = "menu"; + const report = await AbuseReporter.createAbuseReport(ADDON_ID, { + reportEntryPoint, + }); + + equal(report.addon, addon, "Got the expected addon property"); + equal( + report.reportEntryPoint, + reportEntryPoint, + "Got the expected reportEntryPoint" + ); + + const baseReportData = await AbuseReporter.getReportData(addon); + const reportProperties = { + message: "test message", + reason: "test-reason", + }; + + info("Submitting report"); + report.setMessage(reportProperties.message); + report.setReason(reportProperties.reason); + await report.submit(); + + const expectedEntries = Object.entries({ + report_entry_point: reportEntryPoint, + ...baseReportData, + ...reportProperties, + }); + + for (const [expectedKey, expectedValue] of expectedEntries) { + equal( + reportSubmitted[expectedKey], + expectedValue, + `Got the expected submitted value for "${expectedKey}"` + ); + } + + TelemetryTestUtils.assertEvents( + [ + { + object: reportEntryPoint, + value: ADDON_ID, + extra: { addon_type: "extension" }, + }, + ], + TELEMETRY_EVENTS_FILTERS + ); + + await extension.unload(); +}); + +add_task(async function test_error_recent_submit() { + Services.telemetry.clearEvents(); + clearAbuseReportState(); + + let reportSubmitted; + apiRequestHandler = ({ data, request, response }) => { + reportSubmitted = JSON.parse(data); + handleSubmitRequest({ request, response }); + }; + + const { extension } = await installTestExtension(); + const report = await AbuseReporter.createAbuseReport(ADDON_ID, { + reportEntryPoint: "uninstall", + }); + + const { extension: extension2 } = await installTestExtension({ + manifest: { + browser_specific_settings: { gecko: { id: ADDON_ID2 } }, + name: "Test Extension2", + }, + }); + const report2 = await AbuseReporter.createAbuseReport( + ADDON_ID2, + REPORT_OPTIONS + ); + + // Submit the two reports in fast sequence. + report.setReason("reason1"); + report2.setReason("reason2"); + await report.submit(); + await assertRejectsAbuseReportError(report2.submit(), "ERROR_RECENT_SUBMIT"); + equal( + reportSubmitted.reason, + "reason1", + "Server only received the data from the first submission" + ); + + TelemetryTestUtils.assertEvents( + [ + { + object: "uninstall", + value: ADDON_ID, + extra: { addon_type: "extension" }, + }, + { + object: REPORT_OPTIONS.reportEntryPoint, + value: ADDON_ID2, + extra: { + addon_type: "extension", + error_type: "ERROR_RECENT_SUBMIT", + }, + }, + ], + TELEMETRY_EVENTS_FILTERS + ); + + await extension.unload(); + await extension2.unload(); +}); + +add_task(async function test_submission_server_error() { + const { extension } = await installTestExtension(); + + async function testErrorCode({ + responseStatus, + responseText = "", + expectedErrorType, + expectedErrorInfo, + expectRequest = true, + }) { + info( + `Test expected AbuseReportError on response status "${responseStatus}"` + ); + Services.telemetry.clearEvents(); + clearAbuseReportState(); + + let requestReceived = false; + apiRequestHandler = ({ request, response }) => { + requestReceived = true; + response.setStatusLine(request.httpVersion, responseStatus, "Error"); + response.write(responseText); + }; + + const report = await AbuseReporter.createAbuseReport( + ADDON_ID, + REPORT_OPTIONS + ); + report.setReason("a-reason"); + const promiseSubmit = report.submit(); + if (typeof expectedErrorType === "string") { + // Assert a specific AbuseReportError errorType. + await assertRejectsAbuseReportError( + promiseSubmit, + expectedErrorType, + expectedErrorInfo + ); + } else { + // Assert on a given Error class. + await Assert.rejects(promiseSubmit, expectedErrorType); + } + equal( + requestReceived, + expectRequest, + `${expectRequest ? "" : "Not "}received a request as expected` + ); + + TelemetryTestUtils.assertEvents( + [ + { + object: REPORT_OPTIONS.reportEntryPoint, + value: ADDON_ID, + extra: { + addon_type: "extension", + error_type: + typeof expectedErrorType === "string" + ? expectedErrorType + : "ERROR_UNKNOWN", + }, + }, + ], + TELEMETRY_EVENTS_FILTERS + ); + } + + await testErrorCode({ + responseStatus: 500, + responseText: "A server error", + expectedErrorType: "ERROR_SERVER", + expectedErrorInfo: JSON.stringify({ + status: 500, + responseText: "A server error", + }), + }); + await testErrorCode({ + responseStatus: 404, + responseText: "Not found error", + expectedErrorType: "ERROR_CLIENT", + expectedErrorInfo: JSON.stringify({ + status: 404, + responseText: "Not found error", + }), + }); + // Test response with unexpected status code. + await testErrorCode({ + responseStatus: 604, + responseText: "An unexpected status code", + expectedErrorType: "ERROR_UNKNOWN", + expectedErrorInfo: JSON.stringify({ + status: 604, + responseText: "An unexpected status code", + }), + }); + // Test response status 200 with invalid json data. + await testErrorCode({ + responseStatus: 200, + expectedErrorType: /SyntaxError: JSON.parse/, + }); + + // Test on invalid url. + Services.prefs.setCharPref( + "extensions.abuseReport.url", + "invalid-protocol://abuse-report" + ); + await testErrorCode({ + expectedErrorType: "ERROR_NETWORK", + expectRequest: false, + }); + + await extension.unload(); +}); + +add_task(async function set_test_abusereport_url() { + Services.prefs.setCharPref( + "extensions.abuseReport.url", + "http://test.addons.org/api/report/" + ); +}); + +add_task(async function test_submission_aborting() { + Services.telemetry.clearEvents(); + clearAbuseReportState(); + + const { extension } = await installTestExtension(); + + // override the api request handler with one that is never going to reply. + let receivedRequestsCount = 0; + let resolvePendingResponses; + const waitToReply = new Promise( + resolve => (resolvePendingResponses = resolve) + ); + + const onRequestReceived = new Promise(resolve => { + apiRequestHandler = ({ request, response }) => { + response.processAsync(); + response.setStatusLine(request.httpVersion, 200, "OK"); + receivedRequestsCount++; + resolve(); + + // Keep the request pending until resolvePendingResponses have been + // called. + waitToReply.then(() => { + response.finish(); + }); + }; + }); + + const report = await AbuseReporter.createAbuseReport( + ADDON_ID, + REPORT_OPTIONS + ); + report.setReason("a-reason"); + const promiseResult = report.submit(); + + await onRequestReceived; + + ok(receivedRequestsCount > 0, "Got the expected number of requests"); + ok( + (await Promise.race([promiseResult, Promise.resolve("pending")])) === + "pending", + "Submission fetch request should still be pending" + ); + + report.abort(); + + await assertRejectsAbuseReportError(promiseResult, "ERROR_ABORTED_SUBMIT"); + + TelemetryTestUtils.assertEvents( + [ + { + object: REPORT_OPTIONS.reportEntryPoint, + value: ADDON_ID, + extra: { addon_type: "extension", error_type: "ERROR_ABORTED_SUBMIT" }, + }, + ], + TELEMETRY_EVENTS_FILTERS + ); + + await extension.unload(); + + // Unblock pending requests on the server request handler side, so that the + // test file can shutdown (otherwise the test run will be stuck after this + // task completed). + resolvePendingResponses(); +}); + +add_task(async function test_truncated_string_properties() { + const generateString = len => new Array(len).fill("a").join(""); + + const LONG_STRINGS_ADDON_ID = "addon-with-long-strings-props@mochi.test"; + const { extension } = await installTestExtension({ + manifest: { + name: generateString(400), + description: generateString(400), + browser_specific_settings: { gecko: { id: LONG_STRINGS_ADDON_ID } }, + }, + }); + + // Override the test api server request handler, to be able to + // intercept the properties actually submitted. + let reportSubmitted; + apiRequestHandler = ({ data, request, response }) => { + reportSubmitted = JSON.parse(data); + handleSubmitRequest({ request, response }); + }; + + const report = await AbuseReporter.createAbuseReport( + LONG_STRINGS_ADDON_ID, + REPORT_OPTIONS + ); + + report.setMessage("fake-message"); + report.setReason("fake-reason"); + await report.submit(); + + const expected = { + addon_name: generateString(255), + addon_summary: generateString(255), + }; + + Assert.deepEqual( + { + addon_name: reportSubmitted.addon_name, + addon_summary: reportSubmitted.addon_summary, + }, + expected, + "Got the long strings truncated as expected" + ); + + await extension.unload(); +}); + +add_task(async function test_report_recommended() { + const NON_RECOMMENDED_ADDON_ID = "non-recommended-addon@mochi.test"; + const RECOMMENDED_ADDON_ID = "recommended-addon@mochi.test"; + + const now = Date.now(); + const not_before = new Date(now - 3600000).toISOString(); + const not_after = new Date(now + 3600000).toISOString(); + + const { extension: nonRecommended } = await installTestExtension({ + manifest: { + name: "Fake non recommended addon", + browser_specific_settings: { gecko: { id: NON_RECOMMENDED_ADDON_ID } }, + }, + }); + + const { extension: recommended } = await installTestExtension({ + manifest: { + name: "Fake recommended addon", + browser_specific_settings: { gecko: { id: RECOMMENDED_ADDON_ID } }, + }, + files: { + "mozilla-recommendation.json": { + addon_id: RECOMMENDED_ADDON_ID, + states: ["recommended"], + validity: { not_before, not_after }, + }, + }, + }); + + // Override the test api server request handler, to be able to + // intercept the properties actually submitted. + let reportSubmitted; + apiRequestHandler = ({ data, request, response }) => { + reportSubmitted = JSON.parse(data); + handleSubmitRequest({ request, response }); + }; + + async function checkReportedSignature(addonId, expectedAddonSignature) { + clearAbuseReportState(); + const report = await AbuseReporter.createAbuseReport( + addonId, + REPORT_OPTIONS + ); + report.setMessage("fake-message"); + report.setReason("fake-reason"); + await report.submit(); + equal( + reportSubmitted.addon_signature, + expectedAddonSignature, + `Got the expected addon_signature for ${addonId}` + ); + } + + await checkReportedSignature(NON_RECOMMENDED_ADDON_ID, "signed"); + await checkReportedSignature(RECOMMENDED_ADDON_ID, "curated"); + + await nonRecommended.unload(); + await recommended.unload(); +}); + +add_task(async function test_query_amo_details() { + async function assertReportOnAMODetails({ + addonId, + addonType = "extension", + expectedReport, + } = {}) { + // Clear last report timestamp and any telemetry event recorded so far. + clearAbuseReportState(); + Services.telemetry.clearEvents(); + + const report = await AbuseReporter.createAbuseReport(addonId, { + reportEntryPoint: "menu", + }); + + let reportSubmitted; + apiRequestHandler = ({ data, request, response }) => { + reportSubmitted = JSON.parse(data); + handleSubmitRequest({ request, response }); + }; + + report.setMessage("fake message"); + report.setReason("reason1"); + await report.submit(); + + Assert.deepEqual( + expectedReport, + getProperties(reportSubmitted, Object.keys(expectedReport)), + "Got the expected report properties" + ); + + // Telemetry recorded for the successfully submitted report. + TelemetryTestUtils.assertEvents( + [ + { + object: "menu", + value: addonId, + extra: { addon_type: FAKE_AMO_DETAILS.type }, + }, + ], + TELEMETRY_EVENTS_FILTERS + ); + + clearAbuseReportState(); + } + + // Add the expected AMO addons details. + const addonId = "not-installed-addon@mochi.test"; + amoAddonDetailsMap.set(addonId, FAKE_AMO_DETAILS); + + // Test on the default en-US locale. + Services.prefs.setCharPref(PREF_REQUIRED_LOCALE, "en-US"); + let locale = Services.locale.appLocaleAsBCP47; + equal(locale, "en-US", "Got the expected app locale set"); + + let expectedReport = { + addon: addonId, + addon_name: FAKE_AMO_DETAILS.name[locale], + addon_version: FAKE_AMO_DETAILS.current_version.version, + addon_install_source: "not_installed", + addon_install_method: null, + addon_signature: "curated", + }; + + await assertReportOnAMODetails({ addonId, expectedReport }); + + // Test with a non-default locale also available in the AMO details. + Services.prefs.setCharPref(PREF_REQUIRED_LOCALE, "it-IT"); + locale = Services.locale.appLocaleAsBCP47; + equal(locale, "it-IT", "Got the expected app locale set"); + + expectedReport = { + ...expectedReport, + addon_name: FAKE_AMO_DETAILS.name[locale], + }; + await assertReportOnAMODetails({ addonId, expectedReport }); + + // Test with a non-default locale not available in the AMO details. + Services.prefs.setCharPref(PREF_REQUIRED_LOCALE, "fr-FR"); + locale = Services.locale.appLocaleAsBCP47; + equal(locale, "fr-FR", "Got the expected app locale set"); + + expectedReport = { + ...expectedReport, + // Fallbacks on en-US for non available locales. + addon_name: FAKE_AMO_DETAILS.name["en-US"], + }; + await assertReportOnAMODetails({ addonId, expectedReport }); + + Services.prefs.clearUserPref(PREF_REQUIRED_LOCALE); + + amoAddonDetailsMap.clear(); +}); + +add_task(async function test_statictheme_normalized_into_type_theme() { + const themeId = "not-installed-statictheme@mochi.test"; + amoAddonDetailsMap.set(themeId, { + ...FAKE_AMO_DETAILS, + type: "statictheme", + }); + + const report = await AbuseReporter.createAbuseReport(themeId, REPORT_OPTIONS); + + equal(report.addon.id, themeId, "Got a report for the expected theme id"); + equal(report.addon.type, "theme", "Got the expected addon type"); + + amoAddonDetailsMap.clear(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js new file mode 100644 index 0000000000..2e1e37aa3d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js @@ -0,0 +1,316 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests AddonRepository.jsm + +var gServer = createHttpServer({ hosts: ["example.com"] }); + +const PREF_GETADDONS_BROWSEADDONS = "extensions.getAddons.browseAddons"; +const PREF_GETADDONS_BROWSESEARCHRESULTS = + "extensions.getAddons.search.browseURL"; + +const BASE_URL = "http://example.com"; +const DEFAULT_URL = "about:blank"; + +const ADDONS = [ + { + manifest: { + name: "XPI Add-on 1", + version: "1.1", + browser_specific_settings: { + gecko: { id: "test_AddonRepository_1@tests.mozilla.org" }, + }, + }, + }, + { + manifest: { + name: "XPI Add-on 2", + version: "1.2", + theme: {}, + browser_specific_settings: { + gecko: { id: "test_AddonRepository_2@tests.mozilla.org" }, + }, + }, + }, + { + manifest: { + name: "XPI Add-on 3", + version: "1.3", + theme: {}, + browser_specific_settings: { + gecko: { id: "test_AddonRepository_3@tests.mozilla.org" }, + }, + }, + }, +]; + +// Path to source URI of installing add-on +const INSTALL_URL2 = "/addons/test_AddonRepository_2.xpi"; +// Path to source URI of non-active add-on (state = STATE_AVAILABLE) +const INSTALL_URL3 = "/addons/test_AddonRepository_3.xpi"; + +// Properties of an individual add-on that should be checked +// Note: name is checked separately +var ADDON_PROPERTIES = [ + "id", + "type", + "version", + "creator", + "developers", + "description", + "fullDescription", + "iconURL", + "icons", + "screenshots", + "supportURL", + "contributionURL", + "averageRating", + "reviewCount", + "reviewURL", + "weeklyDownloads", + "dailyUsers", + "sourceURI", + "updateDate", +]; + +// Results of getAddonsByIDs +var GET_RESULTS = [ + { + id: "test1@tests.mozilla.org", + type: "extension", + version: "1.1", + creator: { + name: "Test Creator 1", + url: BASE_URL + "/creator1.html", + }, + developers: [ + { + name: "Test Developer 1", + url: BASE_URL + "/developer1.html", + }, + ], + description: "Test Summary 1", + fullDescription: "Test Description 1", + iconURL: BASE_URL + "/icon1.png", + icons: { 32: BASE_URL + "/icon1.png" }, + screenshots: [ + { + url: BASE_URL + "/full1-1.png", + width: 400, + height: 300, + thumbnailURL: BASE_URL + "/thumbnail1-1.png", + thumbnailWidth: 200, + thumbnailHeight: 150, + caption: "Caption 1 - 1", + }, + { + url: BASE_URL + "/full2-1.png", + thumbnailURL: BASE_URL + "/thumbnail2-1.png", + caption: "Caption 2 - 1", + }, + ], + supportURL: BASE_URL + "/support1.html", + contributionURL: BASE_URL + "/contribution1.html", + averageRating: 4, + reviewCount: 1111, + reviewURL: BASE_URL + "/review1.html", + weeklyDownloads: 3333, + sourceURI: BASE_URL + INSTALL_URL2, + updateDate: new Date(1265033045000), + }, + { + id: "test2@tests.mozilla.org", + type: "extension", + version: "2.0", + icons: {}, + sourceURI: "http://example.com/addons/bleah.xpi", + }, + { + id: "test_AddonRepository_1@tests.mozilla.org", + type: "theme", + version: "1.4", + icons: {}, + }, +]; + +// Values for testing AddonRepository.getAddonsByIDs() +var GET_TEST = { + preference: PREF_GETADDONS_BYIDS, + preferenceValue: BASE_URL + "/%OS%/%VERSION%/%IDS%", + failedIDs: ["test1@tests.mozilla.org"], + failedURL: "/XPCShell/1/test1%40tests.mozilla.org", + successfulIDs: [ + "test1@tests.mozilla.org", + "test2@tests.mozilla.org", + "{00000000-1111-2222-3333-444444444444}", + "test_AddonRepository_1@tests.mozilla.org", + ], + successfulURL: + "/XPCShell/1/test1%40tests.mozilla.org%2C" + + "test2%40tests.mozilla.org%2C" + + "%7B00000000-1111-2222-3333-444444444444%7D%2C" + + "test_AddonRepository_1%40tests.mozilla.org", + successfulRTAURL: + "/XPCShell/1/rta%3AdGVzdDFAdGVzdHMubW96aWxsYS5vcmc%2C" + + "test2%40tests.mozilla.org%2C" + + "%7B00000000-1111-2222-3333-444444444444%7D%2C" + + "test_AddonRepository_1%40tests.mozilla.org", +}; + +// Test that actual results and expected results are equal +function check_results(aActualAddons, aExpectedAddons) { + do_check_addons(aActualAddons, aExpectedAddons, ADDON_PROPERTIES); + + // Additional tests + aActualAddons.forEach(function check_each_addon(aActualAddon) { + // Separately check name so better messages are output when test fails + if (aActualAddon.name == "FAIL") { + do_throw(aActualAddon.id + " - " + aActualAddon.description); + } + if (aActualAddon.name != "PASS") { + do_throw(aActualAddon.id + " - invalid add-on name " + aActualAddon.name); + } + }); +} + +add_task(async function setup() { + // Setup for test + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); + + let xpis = ADDONS.map(addon => createTempWebExtensionFile(addon)); + + // Register other add-on XPI files + gServer.registerFile(INSTALL_URL2, xpis[1]); + gServer.registerFile(INSTALL_URL3, xpis[2]); + + // Register files used to test search failure + gServer.registerFile( + GET_TEST.failedURL, + do_get_file("data/test_AddonRepository_fail.json") + ); + + // Register files used to test search success + gServer.registerFile( + GET_TEST.successfulURL, + do_get_file("data/test_AddonRepository_getAddonsByIDs.json") + ); + // Register file for RTA test + gServer.registerFile( + GET_TEST.successfulRTAURL, + do_get_file("data/test_AddonRepository_getAddonsByIDs.json") + ); + + await promiseStartupManager(); + + // Install an add-on so can check that it isn't returned in the results + await promiseInstallFile(xpis[0]); + await promiseRestartManager(); + + // Create an active AddonInstall so can check that it isn't returned in the results + let install = await AddonManager.getInstallForURL(BASE_URL + INSTALL_URL2); + let promise = promiseCompleteInstall(install); + registerCleanupFunction(() => promise); + + // Create a non-active AddonInstall so can check that it is returned in the results + await AddonManager.getInstallForURL(BASE_URL + INSTALL_URL3); +}); + +// Tests homepageURL and getSearchURL() +add_task(async function test_1() { + function check_urls(aPreference, aGetURL, aTests) { + aTests.forEach(function (aTest) { + Services.prefs.setCharPref(aPreference, aTest.preferenceValue); + Assert.equal(aGetURL(aTest), aTest.expectedURL); + }); + } + + var urlTests = [ + { + preferenceValue: BASE_URL, + expectedURL: BASE_URL, + }, + { + preferenceValue: BASE_URL + "/%OS%/%VERSION%", + expectedURL: BASE_URL + "/XPCShell/1", + }, + ]; + + // Extra tests for AddonRepository.getSearchURL(); + var searchURLTests = [ + { + searchTerms: "test", + preferenceValue: BASE_URL + "/search?q=%TERMS%", + expectedURL: BASE_URL + "/search?q=test", + }, + { + searchTerms: "test search", + preferenceValue: BASE_URL + "/%TERMS%", + expectedURL: BASE_URL + "/test%20search", + }, + { + searchTerms: 'odd=search:with&weird"characters', + preferenceValue: BASE_URL + "/%TERMS%", + expectedURL: BASE_URL + "/odd%3Dsearch%3Awith%26weird%22characters", + }, + ]; + + // Setup tests for homepageURL and getSearchURL() + var tests = [ + { + initiallyUndefined: true, + preference: PREF_GETADDONS_BROWSEADDONS, + urlTests, + getURL: () => AddonRepository.homepageURL, + }, + { + initiallyUndefined: false, + preference: PREF_GETADDONS_BROWSESEARCHRESULTS, + urlTests: urlTests.concat(searchURLTests), + getURL: function getSearchURL(aTest) { + var searchTerms = + aTest && aTest.searchTerms ? aTest.searchTerms : "unused terms"; + return AddonRepository.getSearchURL(searchTerms); + }, + }, + ]; + + tests.forEach(function url_test(aTest) { + if (aTest.initiallyUndefined) { + // Preference is not defined by default + Assert.equal( + Services.prefs.getPrefType(aTest.preference), + Services.prefs.PREF_INVALID + ); + Assert.equal(aTest.getURL(), DEFAULT_URL); + } + + check_urls(aTest.preference, aTest.getURL, aTest.urlTests); + }); +}); + +// Tests failure of AddonRepository.getAddonsByIDs() +add_task(async function test_getAddonsByID_fails() { + Services.prefs.setCharPref(GET_TEST.preference, GET_TEST.preferenceValue); + + await Assert.rejects( + AddonRepository.getAddonsByIDs(GET_TEST.failedIDs), + /Error: GET.*?failed/ + ); +}); + +// Tests success of AddonRepository.getAddonsByIDs() +add_task(async function test_getAddonsByID_succeeds() { + let result = await AddonRepository.getAddonsByIDs(GET_TEST.successfulIDs); + + check_results(result, GET_RESULTS); +}); + +// Tests success of AddonRepository.getAddonsByIDs() with rta ID. +add_task(async function test_getAddonsByID_rta() { + let id = `rta:${btoa(GET_TEST.successfulIDs[0])}`.slice(0, -1); + GET_TEST.successfulIDs[0] = id; + let result = await AddonRepository.getAddonsByIDs(GET_TEST.successfulIDs); + + check_results(result, GET_RESULTS); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js new file mode 100644 index 0000000000..5c4e873fef --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -0,0 +1,714 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests caching in AddonRepository.jsm + +var gServer; + +const HOST = "example.com"; +const BASE_URL = "http://example.com"; + +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; +const PREF_GETADDONS_CACHE_TYPES = "extensions.getAddons.cache.types"; +const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.json"; +const EMPTY_RESULT = BASE_URL + "/data/test_AddonRepository_empty.json"; +const FAILED_RESULT = BASE_URL + "/data/test_AddonRepository_fail.json"; + +const FILE_DATABASE = "addons.json"; + +const ADDONS = [ + { + manifest: { + name: "XPI Add-on 1", + version: "1.1", + + description: "XPI Add-on 1 - Description", + developer: { + name: "XPI Add-on 1 - Author", + }, + + homepage_url: "http://example.com/xpi/1/homepage.html", + icons: { + 32: "icon.png", + }, + + options_ui: { + page: "options.html", + }, + + browser_specific_settings: { + gecko: { id: "test_AddonRepository_1@tests.mozilla.org" }, + }, + }, + }, + { + manifest: { + name: "XPI Add-on 2", + version: "1.2", + theme: {}, + browser_specific_settings: { + gecko: { id: "test_AddonRepository_2@tests.mozilla.org" }, + }, + }, + }, + { + manifest: { + name: "XPI Add-on 3", + version: "1.3", + icons: { + 32: "icon.png", + }, + theme: {}, + browser_specific_settings: { + gecko: { id: "test_AddonRepository_3@tests.mozilla.org" }, + }, + }, + files: { + "preview.png": "", + }, + }, +]; + +const ADDON_IDS = ADDONS.map( + addon => addon.manifest.browser_specific_settings.gecko.id +); +const ADDON_FILES = ADDONS.map(addon => + AddonTestUtils.createTempWebExtensionFile(addon) +); + +const PREF_ADDON0_CACHE_ENABLED = + "extensions." + ADDON_IDS[0] + ".getAddons.cache.enabled"; +const PREF_ADDON1_CACHE_ENABLED = + "extensions." + ADDON_IDS[1] + ".getAddons.cache.enabled"; + +// Properties of an individual add-on that should be checked +// Note: updateDate is checked separately +const ADDON_PROPERTIES = [ + "id", + "type", + "name", + "version", + "developers", + "description", + "fullDescription", + "icons", + "screenshots", + "homepageURL", + "supportURL", + "optionsURL", + "averageRating", + "reviewCount", + "reviewURL", + "weeklyDownloads", + "sourceURI", +]; + +// The updateDate property is annoying to test for XPI add-ons. +// However, since we only care about whether the repository value vs. the +// XPI value is used, we can just test if the property value matches +// the repository value +const REPOSITORY_UPDATEDATE = 9; + +// Get the URI of a subfile locating directly in the folder of +// the add-on corresponding to the specified id +function get_subfile_uri(aId, aFilename) { + let file = gProfD.clone(); + file.append("extensions"); + return do_get_addon_root_uri(file, aId) + aFilename; +} + +// Expected repository add-ons +const REPOSITORY_ADDONS = [ + { + id: ADDON_IDS[0], + type: "extension", + name: "Repo Add-on 1", + version: "2.1", + developers: [ + { + name: "Repo Add-on 1 - First Developer", + url: BASE_URL + "/repo/1/firstDeveloper.html", + }, + { + name: "Repo Add-on 1 - Second Developer", + url: BASE_URL + "/repo/1/secondDeveloper.html", + }, + ], + description: "Repo Add-on 1 - Description\nSecond line", + fullDescription: "Repo Add-on 1 - Full Description & some extra", + icons: { 32: BASE_URL + "/repo/1/icon.png" }, + homepageURL: BASE_URL + "/repo/1/homepage.html", + supportURL: BASE_URL + "/repo/1/support.html", + averageRating: 1, + reviewCount: 1111, + reviewURL: BASE_URL + "/repo/1/review.html", + weeklyDownloads: 3331, + sourceURI: BASE_URL + "/repo/1/install.xpi", + }, + { + id: ADDON_IDS[1], + type: "theme", + name: "Repo Add-on 2", + version: "2.2", + developers: [ + { + name: "Repo Add-on 2 - First Developer", + url: BASE_URL + "/repo/2/firstDeveloper.html", + }, + { + name: "Repo Add-on 2 - Second Developer", + url: BASE_URL + "/repo/2/secondDeveloper.html", + }, + ], + description: "Repo Add-on 2 - Description", + fullDescription: "Repo Add-on 2 - Full Description", + icons: { 32: BASE_URL + "/repo/2/icon.png" }, + screenshots: [ + { + url: BASE_URL + "/repo/2/firstFull.png", + thumbnailURL: BASE_URL + "/repo/2/firstThumbnail.png", + caption: "Repo Add-on 2 - First Caption", + }, + { + url: BASE_URL + "/repo/2/secondFull.png", + thumbnailURL: BASE_URL + "/repo/2/secondThumbnail.png", + caption: "Repo Add-on 2 - Second Caption", + }, + ], + homepageURL: BASE_URL + "/repo/2/homepage.html", + supportURL: BASE_URL + "/repo/2/support.html", + averageRating: 2, + reviewCount: 1112, + reviewURL: BASE_URL + "/repo/2/review.html", + weeklyDownloads: 3332, + sourceURI: BASE_URL + "/repo/2/install.xpi", + }, + { + id: ADDON_IDS[2], + type: "theme", + name: "Repo Add-on 3", + version: "2.3", + icons: { 32: BASE_URL + "/repo/3/icon.png" }, + screenshots: [ + { + url: BASE_URL + "/repo/3/firstFull.png", + thumbnailURL: BASE_URL + "/repo/3/firstThumbnail.png", + caption: "Repo Add-on 3 - First Caption", + }, + { + url: BASE_URL + "/repo/3/secondFull.png", + thumbnailURL: BASE_URL + "/repo/3/secondThumbnail.png", + caption: "Repo Add-on 3 - Second Caption", + }, + ], + }, +]; + +function extensionURL(id, path) { + return WebExtensionPolicy.getByID(id).getURL(path); +} + +// Expected add-ons when not using cache +const WITHOUT_CACHE = [ + { + id: ADDON_IDS[0], + type: "extension", + name: "XPI Add-on 1", + version: "1.1", + authors: [{ name: "XPI Add-on 1 - Author" }], + description: "XPI Add-on 1 - Description", + get icons() { + return { 32: get_subfile_uri(ADDON_IDS[0], "icon.png") }; + }, + homepageURL: `${BASE_URL}/xpi/1/homepage.html`, + get optionsURL() { + return extensionURL(ADDON_IDS[0], "options.html"); + }, + sourceURI: NetUtil.newURI(ADDON_FILES[0]).spec, + }, + { + id: ADDON_IDS[1], + type: "theme", + name: "XPI Add-on 2", + version: "1.2", + sourceURI: NetUtil.newURI(ADDON_FILES[1]).spec, + icons: {}, + }, + { + id: ADDON_IDS[2], + type: "theme", + name: "XPI Add-on 3", + version: "1.3", + get icons() { + return { 32: get_subfile_uri(ADDON_IDS[2], "icon.png") }; + }, + screenshots: [ + { + get url() { + return get_subfile_uri(ADDON_IDS[2], "preview.png"); + }, + }, + ], + sourceURI: NetUtil.newURI(ADDON_FILES[2]).spec, + }, +]; + +// Expected add-ons when using cache +const WITH_CACHE = [ + { + id: ADDON_IDS[0], + type: "extension", + name: "XPI Add-on 1", + version: "1.1", + developers: [ + { + name: "Repo Add-on 1 - First Developer", + url: BASE_URL + "/repo/1/firstDeveloper.html", + }, + { + name: "Repo Add-on 1 - Second Developer", + url: BASE_URL + "/repo/1/secondDeveloper.html", + }, + ], + description: "XPI Add-on 1 - Description", + fullDescription: "Repo Add-on 1 - Full Description & some extra", + get icons() { + return { 32: get_subfile_uri(ADDON_IDS[0], "icon.png") }; + }, + homepageURL: BASE_URL + "/xpi/1/homepage.html", + supportURL: BASE_URL + "/repo/1/support.html", + get optionsURL() { + return extensionURL(ADDON_IDS[0], "options.html"); + }, + averageRating: 1, + reviewCount: 1111, + reviewURL: BASE_URL + "/repo/1/review.html", + weeklyDownloads: 3331, + sourceURI: NetUtil.newURI(ADDON_FILES[0]).spec, + }, + { + id: ADDON_IDS[1], + type: "theme", + name: "XPI Add-on 2", + version: "1.2", + developers: [ + { + name: "Repo Add-on 2 - First Developer", + url: BASE_URL + "/repo/2/firstDeveloper.html", + }, + { + name: "Repo Add-on 2 - Second Developer", + url: BASE_URL + "/repo/2/secondDeveloper.html", + }, + ], + description: "Repo Add-on 2 - Description", + fullDescription: "Repo Add-on 2 - Full Description", + icons: { 32: BASE_URL + "/repo/2/icon.png" }, + screenshots: [ + { + url: BASE_URL + "/repo/2/firstFull.png", + thumbnailURL: BASE_URL + "/repo/2/firstThumbnail.png", + caption: "Repo Add-on 2 - First Caption", + }, + { + url: BASE_URL + "/repo/2/secondFull.png", + thumbnailURL: BASE_URL + "/repo/2/secondThumbnail.png", + caption: "Repo Add-on 2 - Second Caption", + }, + ], + homepageURL: BASE_URL + "/repo/2/homepage.html", + supportURL: BASE_URL + "/repo/2/support.html", + averageRating: 2, + reviewCount: 1112, + reviewURL: BASE_URL + "/repo/2/review.html", + weeklyDownloads: 3332, + sourceURI: NetUtil.newURI(ADDON_FILES[1]).spec, + }, + { + id: ADDON_IDS[2], + type: "theme", + name: "XPI Add-on 3", + version: "1.3", + get iconURL() { + return get_subfile_uri(ADDON_IDS[2], "icon.png"); + }, + get icons() { + return { 32: get_subfile_uri(ADDON_IDS[2], "icon.png") }; + }, + screenshots: [ + { + url: BASE_URL + "/repo/3/firstFull.png", + thumbnailURL: BASE_URL + "/repo/3/firstThumbnail.png", + caption: "Repo Add-on 3 - First Caption", + }, + { + url: BASE_URL + "/repo/3/secondFull.png", + thumbnailURL: BASE_URL + "/repo/3/secondThumbnail.png", + caption: "Repo Add-on 3 - Second Caption", + }, + ], + sourceURI: NetUtil.newURI(ADDON_FILES[2]).spec, + }, +]; + +// Expected add-ons when using cache +const WITH_EXTENSION_CACHE = [ + { + id: ADDON_IDS[0], + type: "extension", + name: "XPI Add-on 1", + version: "1.1", + developers: [ + { + name: "Repo Add-on 1 - First Developer", + url: BASE_URL + "/repo/1/firstDeveloper.html", + }, + { + name: "Repo Add-on 1 - Second Developer", + url: BASE_URL + "/repo/1/secondDeveloper.html", + }, + ], + description: "XPI Add-on 1 - Description", + fullDescription: "Repo Add-on 1 - Full Description & some extra", + get icons() { + return { 32: get_subfile_uri(ADDON_IDS[0], "icon.png") }; + }, + homepageURL: BASE_URL + "/xpi/1/homepage.html", + supportURL: BASE_URL + "/repo/1/support.html", + get optionsURL() { + return extensionURL(ADDON_IDS[0], "options.html"); + }, + averageRating: 1, + reviewCount: 1111, + reviewURL: BASE_URL + "/repo/1/review.html", + weeklyDownloads: 3331, + sourceURI: NetUtil.newURI(ADDON_FILES[0]).spec, + }, + { + id: ADDON_IDS[1], + type: "theme", + name: "XPI Add-on 2", + version: "1.2", + sourceURI: NetUtil.newURI(ADDON_FILES[1]).spec, + icons: {}, + }, + { + id: ADDON_IDS[2], + type: "theme", + name: "XPI Add-on 3", + version: "1.3", + get iconURL() { + return get_subfile_uri(ADDON_IDS[2], "icon.png"); + }, + get icons() { + return { 32: get_subfile_uri(ADDON_IDS[2], "icon.png") }; + }, + screenshots: [ + { + get url() { + return get_subfile_uri(ADDON_IDS[2], "preview.png"); + }, + }, + ], + sourceURI: NetUtil.newURI(ADDON_FILES[2]).spec, + }, +]; + +var gDBFile = gProfD.clone(); +gDBFile.append(FILE_DATABASE); + +/* + * Check the actual add-on results against the expected add-on results + * + * @param aActualAddons + * The array of actual add-ons to check + * @param aExpectedAddons + * The array of expected add-ons to check against + * @param aFromRepository + * An optional boolean representing if the add-ons are from + * the repository + */ +function check_results(aActualAddons, aExpectedAddons, aFromRepository) { + aFromRepository = !!aFromRepository; + + do_check_addons(aActualAddons, aExpectedAddons, ADDON_PROPERTIES); + + // Separately test updateDate (it should only be equal to the + // REPOSITORY values if it is from the repository) + aActualAddons.forEach(function (aActualAddon) { + if (aActualAddon.updateDate) { + let time = aActualAddon.updateDate.getTime(); + Assert.equal(time === 1000 * REPOSITORY_UPDATEDATE, aFromRepository); + } + }); +} + +/* + * Check the add-ons in the cache. This function also tests + * AddonRepository.getCachedAddonByID() + * + * @param aExpectedToFind + * An array of booleans representing which REPOSITORY_ADDONS are + * expected to be found in the cache + * @param aExpectedImmediately + * A boolean representing if results from the cache are expected + * immediately. Results are not immediate if the cache has not been + * initialized yet. + * @return Promise{null} + * Resolves once the checks are complete + */ +function check_cache(aExpectedToFind, aExpectedImmediately) { + Assert.equal(aExpectedToFind.length, REPOSITORY_ADDONS.length); + + let lookups = []; + + for (let i = 0; i < REPOSITORY_ADDONS.length; i++) { + lookups.push( + new Promise((resolve, reject) => { + let immediatelyFound = true; + let expected = aExpectedToFind[i] ? REPOSITORY_ADDONS[i] : null; + // can't Promise-wrap this because we're also testing whether the callback is + // sync or async + AddonRepository.getCachedAddonByID( + REPOSITORY_ADDONS[i].id, + function (aAddon) { + Assert.equal(immediatelyFound, aExpectedImmediately); + if (expected == null) { + Assert.equal(aAddon, null); + } else { + check_results([aAddon], [expected], true); + } + resolve(); + } + ); + immediatelyFound = false; + }) + ); + } + return Promise.all(lookups); +} + +/* + * Task to check an initialized cache by checking the cache, then restarting the + * manager, and checking the cache. This checks that the cache is consistent + * across manager restarts. + * + * @param aExpectedToFind + * An array of booleans representing which REPOSITORY_ADDONS are + * expected to be found in the cache + */ +async function check_initialized_cache(aExpectedToFind) { + await check_cache(aExpectedToFind, true); + await promiseRestartManager(); + + // If cache is disabled, then expect results immediately + let cacheEnabled = Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED); + await check_cache(aExpectedToFind, !cacheEnabled); +} + +add_task(async function setup() { + // Setup for test + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); + + await promiseStartupManager(); + + // Install XPI add-ons + await promiseInstallAllFiles(ADDON_FILES); + await promiseRestartManager(); + + gServer = AddonTestUtils.createHttpServer({ hosts: [HOST] }); + gServer.registerDirectory("/data/", do_get_file("data")); +}); + +// Tests AddonRepository.cacheEnabled +add_task(async function run_test_1() { + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + Assert.ok(!AddonRepository.cacheEnabled); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + Assert.ok(AddonRepository.cacheEnabled); +}); + +// Tests that the cache and database begin as empty +add_task(async function run_test_2() { + Assert.ok(!gDBFile.exists()); + await check_cache([false, false, false], false); + await AddonRepository.flush(); +}); + +// Tests repopulateCache when the search fails +add_task(async function run_test_3() { + Assert.ok(gDBFile.exists()); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, FAILED_RESULT); + + await AddonRepository.backgroundUpdateCheck(); + await check_initialized_cache([false, false, false]); +}); + +// Tests repopulateCache when search returns no results +add_task(async function run_test_4() { + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, EMPTY_RESULT); + + await AddonRepository.backgroundUpdateCheck(); + await check_initialized_cache([false, false, false]); +}); + +// Tests repopulateCache when search returns results +add_task(async function run_test_5() { + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); + + await AddonRepository.backgroundUpdateCheck(); + await check_initialized_cache([true, true, true]); +}); + +// Tests repopulateCache when caching is disabled for a single add-on +add_task(async function run_test_5_1() { + Services.prefs.setBoolPref(PREF_ADDON0_CACHE_ENABLED, false); + + await AddonRepository.backgroundUpdateCheck(); + + // Reset pref for next test + Services.prefs.setBoolPref(PREF_ADDON0_CACHE_ENABLED, true); + + await check_initialized_cache([false, true, true]); +}); + +// Tests repopulateCache when caching is disabled +add_task(async function run_test_6() { + Assert.ok(gDBFile.exists()); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + + await AddonRepository.backgroundUpdateCheck(); + // Database should have been deleted + Assert.ok(!gDBFile.exists()); + + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + await check_cache([false, false, false], false); + await AddonRepository.flush(); +}); + +// Tests cacheAddons when the search fails +add_task(async function run_test_7() { + Assert.ok(gDBFile.exists()); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, FAILED_RESULT); + + await AddonRepository.cacheAddons(ADDON_IDS); + await check_initialized_cache([false, false, false]); +}); + +// Tests cacheAddons when the search returns no results +add_task(async function run_test_8() { + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, EMPTY_RESULT); + + await AddonRepository.cacheAddons(ADDON_IDS); + await check_initialized_cache([false, false, false]); +}); + +// Tests cacheAddons for a single add-on when search returns results +add_task(async function run_test_9() { + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); + + await AddonRepository.cacheAddons([ADDON_IDS[0]]); + await check_initialized_cache([true, false, false]); +}); + +// Tests cacheAddons when caching is disabled for a single add-on +add_task(async function run_test_9_1() { + Services.prefs.setBoolPref(PREF_ADDON1_CACHE_ENABLED, false); + + await AddonRepository.cacheAddons(ADDON_IDS); + + // Reset pref for next test + Services.prefs.setBoolPref(PREF_ADDON1_CACHE_ENABLED, true); + + await check_initialized_cache([true, false, true]); +}); + +// Tests cacheAddons for multiple add-ons, some already in the cache, +add_task(async function run_test_10() { + await AddonRepository.cacheAddons(ADDON_IDS); + await check_initialized_cache([true, true, true]); +}); + +// Tests cacheAddons when caching is disabled +add_task(async function run_test_11() { + Assert.ok(gDBFile.exists()); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + + await AddonRepository.cacheAddons(ADDON_IDS); + Assert.ok(gDBFile.exists()); + + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + await check_initialized_cache([true, true, true]); +}); + +// Tests that XPI add-ons do not use any of the repository properties if +// caching is disabled, even if there are repository properties available +add_task(async function run_test_12() { + Assert.ok(gDBFile.exists()); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); + + let addons = await promiseAddonsByIDs(ADDON_IDS); + check_results(addons, WITHOUT_CACHE); +}); + +// Tests that a background update with caching disabled deletes the add-ons +// database, and that XPI add-ons still do not use any of repository properties +add_task(async function run_test_13() { + Assert.ok(gDBFile.exists()); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, EMPTY_RESULT); + + await AddonManagerPrivate.backgroundUpdateCheck(); + // Database should have been deleted + Assert.ok(!gDBFile.exists()); + + let aAddons = await promiseAddonsByIDs(ADDON_IDS); + check_results(aAddons, WITHOUT_CACHE); +}); + +// Tests that the XPI add-ons have the correct properties if caching is +// enabled but has no information +add_task(async function run_test_14() { + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + await AddonManagerPrivate.backgroundUpdateCheck(); + await AddonRepository.flush(); + Assert.ok(gDBFile.exists()); + + let aAddons = await promiseAddonsByIDs(ADDON_IDS); + check_results(aAddons, WITHOUT_CACHE); +}); + +// Tests that the XPI add-ons correctly use the repository properties when +// caching is enabled and the repository information is available +add_task(async function run_test_15() { + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); + + await AddonManagerPrivate.backgroundUpdateCheck(); + let aAddons = await promiseAddonsByIDs(ADDON_IDS); + check_results(aAddons, WITH_CACHE); +}); + +// Tests that restarting the manager does not change the checked properties +// on the XPI add-ons (repository properties still exist and are still properly +// used) +add_task(async function run_test_16() { + await promiseRestartManager(); + + let aAddons = await promiseAddonsByIDs(ADDON_IDS); + check_results(aAddons, WITH_CACHE); +}); + +// Tests that setting a list of types to cache works +add_task(async function run_test_17() { + Services.prefs.setCharPref( + PREF_GETADDONS_CACHE_TYPES, + "foo,bar,extension,baz" + ); + + await AddonManagerPrivate.backgroundUpdateCheck(); + let aAddons = await promiseAddonsByIDs(ADDON_IDS); + check_results(aAddons, WITH_EXTENSION_CACHE); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache_locale.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache_locale.js new file mode 100644 index 0000000000..37e60e27dd --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache_locale.js @@ -0,0 +1,217 @@ +"user strict"; + +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; +const PREF_METADATA_LASTUPDATE = "extensions.getAddons.cache.lastUpdate"; +Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "42", + "42" +); + +let server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); +server.registerDirectory("/data/", do_get_file("data")); + +// Use %LOCALE% as the default pref does. It is set from appLocaleAsBCP47. +Services.prefs.setStringPref( + PREF_GETADDONS_BYIDS, + "http://example.com/addons.json?guids=%IDS%&locale=%LOCALE%" +); + +const TEST_ADDON_ID = "test_AddonRepository_1@tests.mozilla.org"; + +const repositoryAddons = { + "test_AddonRepository_1@tests.mozilla.org": { + name: "Repo Add-on 1", + type: "extension", + guid: TEST_ADDON_ID, + current_version: { + version: "2.1", + files: [ + { + platform: "all", + size: 9, + url: "http://example.com/repo/1/install.xpi", + }, + ], + }, + }, + "langpack-und@test.mozilla.org": { + // included only to avoid exceptions in AddonRepository + name: "und langpack", + type: "language", + guid: "langpack-und@test.mozilla.org", + current_version: { + version: "1.1", + files: [ + { + platform: "all", + size: 9, + url: "http://example.com/repo/1/langpack.xpi", + }, + ], + }, + }, +}; + +server.registerPathHandler("/addons.json", (request, response) => { + let search = new URLSearchParams(request.queryString); + let IDs = search.get("guids").split(","); + let locale = search.get("locale"); + + let repositoryData = { + page_size: 25, + page_count: 1, + count: 0, + next: null, + previous: null, + results: [], + }; + for (let id of IDs) { + let data = JSON.parse(JSON.stringify(repositoryAddons[id])); + data.summary = `This is an ${locale} addon data object`; + data.description = `Full Description ${locale}`; + repositoryData.results.push(data); + } + repositoryData.count = repositoryData.results.length; + + // The request contains the IDs to retreive, but we're just handling the + // two test addons so it's static data. + response.setHeader("content-type", "application/json"); + response.write(JSON.stringify(repositoryData)); +}); + +const ADDONS = [ + { + manifest: { + name: "XPI Add-on 1", + version: "1.1", + + description: "XPI Add-on 1 - Description", + developer: { + name: "XPI Add-on 1 - Author", + }, + + homepage_url: "http://example.com/xpi/1/homepage.html", + icons: { + 32: "icon.png", + }, + + options_ui: { + page: "options.html", + }, + + browser_specific_settings: { + gecko: { id: TEST_ADDON_ID }, + }, + }, + }, + { + // Necessary to provide the "und" locale + manifest: { + name: "und Language Pack", + version: "1.0", + manifest_version: 2, + browser_specific_settings: { + gecko: { + id: "langpack-und@test.mozilla.org", + }, + }, + sources: { + browser: { + base_path: "browser/", + }, + }, + langpack_id: "und", + languages: { + und: { + chrome_resources: { + global: "chrome/und/locale/und/global/", + }, + version: "20171001190118", + }, + }, + author: "Mozilla Localization Task Force", + description: "Language pack for Testy for und", + }, + }, +]; +const ADDON_FILES = ADDONS.map(addon => + AddonTestUtils.createTempWebExtensionFile(addon) +); + +const REQ_LOC_CHANGE_EVENT = "intl:requested-locales-changed"; + +function promiseLocaleChanged(requestedLocale) { + if (Services.locale.appLocaleAsBCP47 == requestedLocale) { + return Promise.resolve(); + } + return new Promise(resolve => { + let localeObserver = { + observe(aSubject, aTopic, aData) { + switch (aTopic) { + case REQ_LOC_CHANGE_EVENT: + let reqLocs = Services.locale.requestedLocales; + equal(reqLocs[0], requestedLocale); + Services.obs.removeObserver(localeObserver, REQ_LOC_CHANGE_EVENT); + resolve(); + } + }, + }; + Services.obs.addObserver(localeObserver, REQ_LOC_CHANGE_EVENT); + Services.locale.requestedLocales = [requestedLocale]; + }); +} + +function promiseMetaDataUpdate() { + return new Promise(resolve => { + let listener = args => { + Services.prefs.removeObserver(PREF_METADATA_LASTUPDATE, listener); + resolve(); + }; + + Services.prefs.addObserver(PREF_METADATA_LASTUPDATE, listener); + }); +} + +function promiseLocale(locale) { + return Promise.all([promiseLocaleChanged(locale), promiseMetaDataUpdate()]); +} + +add_task(async function setup() { + await promiseStartupManager(); + for (let xpi of ADDON_FILES) { + await promiseInstallFile(xpi); + } +}); + +add_task(async function test_locale_change() { + await promiseLocale("en-US"); + let addon = await AddonRepository.getCachedAddonByID(TEST_ADDON_ID); + Assert.ok(addon.description.includes("en-US"), "description is en-us"); + Assert.ok( + addon.fullDescription.includes("en-US"), + "fullDescription is en-us" + ); + + // This pref is a 1s resolution, set it to zero so the + // next test can wait on it being updated again. + Services.prefs.setIntPref(PREF_METADATA_LASTUPDATE, 0); + // Wait for the last update timestamp to be updated. + await promiseLocale("und"); + + addon = await AddonRepository.getCachedAddonByID(TEST_ADDON_ID); + + Assert.ok( + addon.description.includes("und"), + `description is ${addon.description}` + ); + Assert.ok( + addon.fullDescription.includes("und"), + `fullDescription is ${addon.fullDescription}` + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_langpacks.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_langpacks.js new file mode 100644 index 0000000000..e84ce4ea30 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_langpacks.js @@ -0,0 +1,135 @@ +const PREF_GET_LANGPACKS = "extensions.getAddons.langpacks.url"; + +let server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); +Services.prefs.setStringPref( + PREF_GET_LANGPACKS, + "http://example.com/langpacks.json" +); + +add_task(async function test_getlangpacks() { + function setData(data) { + if (typeof data != "string") { + data = JSON.stringify(data); + } + + server.registerPathHandler("/langpacks.json", (request, response) => { + response.setHeader("content-type", "application/json"); + response.write(data); + }); + } + + const EXPECTED = [ + { + target_locale: "kl", + url: "http://example.com/langpack1.xpi", + hash: "sha256:0123456789abcdef", + }, + { + target_locale: "fo", + url: "http://example.com/langpack2.xpi", + hash: "sha256:fedcba9876543210", + }, + ]; + + setData({ + results: [ + // A simple entry + { + target_locale: EXPECTED[0].target_locale, + current_compatible_version: { + files: [ + { + platform: "all", + url: EXPECTED[0].url, + hash: EXPECTED[0].hash, + }, + ], + }, + }, + + // An entry with multiple supported platforms + { + target_locale: EXPECTED[1].target_locale, + current_compatible_version: { + files: [ + { + platform: "somethingelse", + url: "http://example.com/bogus.xpi", + hash: "sha256:abcd", + }, + { + platform: Services.appinfo.OS.toLowerCase(), + url: EXPECTED[1].url, + hash: EXPECTED[1].hash, + }, + ], + }, + }, + + // An entry with no matching platform + { + target_locale: "bla", + current_compatible_version: { + files: [ + { + platform: "unsupportedplatform", + url: "http://example.com/bogus2.xpi", + hash: "sha256:1234", + }, + ], + }, + }, + ], + }); + + let result = await AddonRepository.getAvailableLangpacks(); + equal(result.length, 2, "Got 2 results"); + + deepEqual(result[0], EXPECTED[0], "Got expected result for simple entry"); + deepEqual( + result[1], + EXPECTED[1], + "Got expected result for multi-platform entry" + ); + + setData("not valid json"); + await Assert.rejects( + AddonRepository.getAvailableLangpacks(), + /SyntaxError/, + "Got parse error on invalid JSON" + ); +}); + +// Tests that cookies are not sent with langpack requests. +add_task(async function test_cookies() { + let lastRequest = null; + server.registerPathHandler("/langpacks.json", (request, response) => { + lastRequest = request; + response.write(JSON.stringify({ results: [] })); + }); + + const COOKIE = "test"; + let expiration = Date.now() / 1000 + 60 * 60; + Services.cookies.add( + "example.com", + "/", + COOKIE, + "testing", + false, + false, + false, + expiration, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTP + ); + + await AddonRepository.getAvailableLangpacks(); + + notEqual(lastRequest, null, "Received langpack request"); + equal( + lastRequest.hasHeader("Cookie"), + false, + "Langpack request has no cookies" + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_paging.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_paging.js new file mode 100644 index 0000000000..0773917d84 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_paging.js @@ -0,0 +1,91 @@ +// Test that AMO api results that are returned in muliple pages are +// properly handled. +add_task(async function test_paged_api() { + const MAX_ADDON = 3; + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "2"); + + let testserver = createHttpServer(); + const PORT = testserver.identity.primaryPort; + + const EMPTY_RESPONSE = { + next: null, + results: [], + }; + + function name(n) { + return `Addon ${n}`; + } + function id(n) { + return `test${n}@tests.mozilla.org`; + } + function summary(n) { + return `Summary for addon ${n}`; + } + function description(n) { + return `Description for addon ${n}`; + } + + testserver.registerPathHandler("/empty", (request, response) => { + response.setHeader("content-type", "application/json"); + response.write(JSON.stringify(EMPTY_RESPONSE)); + }); + + testserver.registerPrefixHandler("/addons/", (request, response) => { + let [page] = /\d+/.exec(request.path); + page = page ? parseInt(page, 10) : 0; + page = Math.min(page, MAX_ADDON); + + let result = { + next: + page == MAX_ADDON + ? null + : `http://localhost:${PORT}/addons/${page + 1}`, + results: [ + { + name: name(page), + type: "extension", + guid: id(page), + summary: summary(page), + description: description(page), + }, + ], + }; + + response.setHeader("content-type", "application/json"); + response.write(JSON.stringify(result)); + }); + + Services.prefs.setCharPref( + PREF_GETADDONS_BYIDS, + `http://localhost:${PORT}/addons/0` + ); + + await promiseStartupManager(); + + for (let i = 0; i <= MAX_ADDON; i++) { + await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { gecko: { id: id(i) } }, + }, + }); + } + + await AddonManagerPrivate.backgroundUpdateCheck(); + + let ids = []; + for (let i = 0; i <= MAX_ADDON; i++) { + ids.push(id(i)); + } + let addons = await AddonRepository.getAddonsByIDs(ids); + + equal(addons.length, MAX_ADDON + 1); + for (let i = 0; i <= MAX_ADDON; i++) { + equal(addons[i].name, name(i)); + equal(addons[i].id, id(i)); + equal(addons[i].description, summary(i)); + equal(addons[i].fullDescription, description(i)); + } + + await promiseShutdownManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker.js b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker.js new file mode 100644 index 0000000000..b3fca5bba6 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker.js @@ -0,0 +1,292 @@ +"use strict"; + +const { ProductAddonChecker } = ChromeUtils.importESModule( + "resource://gre/modules/addons/ProductAddonChecker.sys.mjs" +); + +const LocalFile = new Components.Constructor( + "@mozilla.org/file/local;1", + Ci.nsIFile, + "initWithPath" +); + +Services.prefs.setBoolPref("media.gmp-manager.updateEnabled", true); + +var testserver = new HttpServer(); +testserver.registerDirectory("/data/", do_get_file("data/productaddons")); +testserver.start(); +var root = + testserver.identity.primaryScheme + + "://" + + testserver.identity.primaryHost + + ":" + + testserver.identity.primaryPort + + "/data/"; + +/** + * Compares binary data of 2 arrays and returns true if they are the same + * + * @param arr1 The first array to compare + * @param arr2 The second array to compare + */ +function compareBinaryData(arr1, arr2) { + Assert.equal(arr1.length, arr2.length); + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + info( + "Data differs at index " + + i + + ", arr1: " + + arr1[i] + + ", arr2: " + + arr2[i] + ); + return false; + } + } + return true; +} + +/** + * Reads a file's data and returns it + * + * @param file The file to read the data from + * @return array of bytes for the data in the file. + */ +function getBinaryFileData(file) { + let fileStream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + // Open as RD_ONLY with default permissions. + fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); + + let stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + stream.setInputStream(fileStream); + let bytes = stream.readByteArray(stream.available()); + fileStream.close(); + return bytes; +} + +/** + * Compares binary data of 2 files and returns true if they are the same + * + * @param file1 The first file to compare + * @param file2 The second file to compare + */ +function compareFiles(file1, file2) { + return compareBinaryData(getBinaryFileData(file1), getBinaryFileData(file2)); +} + +add_task(async function test_404() { + await Assert.rejects( + ProductAddonChecker.getProductAddonList(root + "404.xml"), + /got node name: html/ + ); +}); + +add_task(async function test_not_xml() { + await Assert.rejects( + ProductAddonChecker.getProductAddonList(root + "bad.txt"), + /got node name: parsererror/ + ); +}); + +add_task(async function test_invalid_xml() { + await Assert.rejects( + ProductAddonChecker.getProductAddonList(root + "bad.xml"), + /got node name: parsererror/ + ); +}); + +add_task(async function test_wrong_xml() { + await Assert.rejects( + ProductAddonChecker.getProductAddonList(root + "bad2.xml"), + /got node name: test/ + ); +}); + +add_task(async function test_missing() { + let addons = await ProductAddonChecker.getProductAddonList( + root + "missing.xml" + ); + Assert.equal(addons, null); +}); + +add_task(async function test_empty() { + let res = await ProductAddonChecker.getProductAddonList(root + "empty.xml"); + Assert.ok(Array.isArray(res.addons)); + Assert.equal(res.addons.length, 0); +}); + +add_task(async function test_good_xml() { + let res = await ProductAddonChecker.getProductAddonList(root + "good.xml"); + Assert.ok(Array.isArray(res.addons)); + + // There are three valid entries in the XML + Assert.equal(res.addons.length, 5); + + let addon = res.addons[0]; + Assert.equal(addon.id, "test1"); + Assert.equal(addon.URL, "http://example.com/test1.xpi"); + Assert.equal(addon.hashFunction, undefined); + Assert.equal(addon.hashValue, undefined); + Assert.equal(addon.version, undefined); + Assert.equal(addon.size, undefined); + + addon = res.addons[1]; + Assert.equal(addon.id, "test2"); + Assert.equal(addon.URL, "http://example.com/test2.xpi"); + Assert.equal(addon.hashFunction, "md5"); + Assert.equal(addon.hashValue, "djhfgsjdhf"); + Assert.equal(addon.version, undefined); + Assert.equal(addon.size, undefined); + + addon = res.addons[2]; + Assert.equal(addon.id, "test3"); + Assert.equal(addon.URL, "http://example.com/test3.xpi"); + Assert.equal(addon.hashFunction, undefined); + Assert.equal(addon.hashValue, undefined); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.size, 45); + + addon = res.addons[3]; + Assert.equal(addon.id, "test4"); + Assert.equal(addon.URL, undefined); + Assert.equal(addon.hashFunction, undefined); + Assert.equal(addon.hashValue, undefined); + Assert.equal(addon.version, undefined); + Assert.equal(addon.size, undefined); + + addon = res.addons[4]; + Assert.equal(addon.id, undefined); + Assert.equal(addon.URL, "http://example.com/test5.xpi"); + Assert.equal(addon.hashFunction, undefined); + Assert.equal(addon.hashValue, undefined); + Assert.equal(addon.version, undefined); + Assert.equal(addon.size, undefined); +}); + +add_task(async function test_download_nourl() { + try { + let path = await ProductAddonChecker.downloadAddon({}); + + await IOUtils.remove(path); + do_throw("Should not have downloaded a file with a missing url"); + } catch (e) { + Assert.ok( + true, + "Should have thrown when downloading a file with a missing url." + ); + } +}); + +add_task(async function test_download_missing() { + try { + let path = await ProductAddonChecker.downloadAddon({ + URL: root + "nofile.xpi", + }); + + await IOUtils.remove(path); + do_throw("Should not have downloaded a missing file"); + } catch (e) { + Assert.ok(true, "Should have thrown when downloading a missing file."); + } +}); + +add_task(async function test_download_noverify() { + let path = await ProductAddonChecker.downloadAddon({ + URL: root + "unsigned.xpi", + }); + + let stat = await IOUtils.stat(path); + Assert.ok(!stat.type !== "directory"); + Assert.equal(stat.size, 452); + + Assert.ok( + compareFiles( + do_get_file("data/productaddons/unsigned.xpi"), + new LocalFile(path) + ) + ); + + await IOUtils.remove(path); +}); + +add_task(async function test_download_badsize() { + try { + let path = await ProductAddonChecker.downloadAddon({ + URL: root + "unsigned.xpi", + size: 400, + }); + + await IOUtils.remove(path); + do_throw("Should not have downloaded a file with a bad size"); + } catch (e) { + Assert.ok( + true, + "Should have thrown when downloading a file with a bad size." + ); + } +}); + +add_task(async function test_download_badhashfn() { + try { + let path = await ProductAddonChecker.downloadAddon({ + URL: root + "unsigned.xpi", + hashFunction: "sha2567", + hashValue: + "9b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3", + }); + + await IOUtils.remove(path); + do_throw("Should not have downloaded a file with a bad hash function"); + } catch (e) { + Assert.ok( + true, + "Should have thrown when downloading a file with a bad hash function." + ); + } +}); + +add_task(async function test_download_badhash() { + try { + let path = await ProductAddonChecker.downloadAddon({ + URL: root + "unsigned.xpi", + hashFunction: "sha256", + hashValue: + "8b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3", + }); + + await IOUtils.remove(path); + do_throw("Should not have downloaded a file with a bad hash"); + } catch (e) { + Assert.ok( + true, + "Should have thrown when downloading a file with a bad hash." + ); + } +}); + +add_task(async function test_download_works() { + let path = await ProductAddonChecker.downloadAddon({ + URL: root + "unsigned.xpi", + size: 452, + hashFunction: "sha256", + hashValue: + "9b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3", + }); + + let stat = await IOUtils.stat(path); + Assert.ok(stat.type !== "directory"); + + Assert.ok( + compareFiles( + do_get_file("data/productaddons/unsigned.xpi"), + new LocalFile(path) + ) + ); + + await IOUtils.remove(path); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js new file mode 100644 index 0000000000..5ae61568ef --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js @@ -0,0 +1,201 @@ +"use strict"; + +const { ProductAddonChecker } = ChromeUtils.importESModule( + "resource://gre/modules/addons/ProductAddonChecker.sys.mjs" +); + +Services.prefs.setBoolPref("media.gmp-manager.updateEnabled", true); + +// Setup a test server for content signature tests. +const signedTestServer = new HttpServer(); +const testDataDir = "data/productaddons/"; + +// Start the server so we can grab the identity. We need to know this so the +// server can reference itself in the handlers that will be set up. +signedTestServer.start(); +const signedBaseUri = + signedTestServer.identity.primaryScheme + + "://" + + signedTestServer.identity.primaryHost + + ":" + + signedTestServer.identity.primaryPort; + +// Setup endpoint to handle x5u lookups correctly. +const validX5uPath = "/valid_x5u"; +// These certificates are generated using ./mach generate-test-certs +const validCertChain = loadCertChain(testDataDir + "content_signing", [ + "aus_ee", + "int", +]); +signedTestServer.registerPathHandler(validX5uPath, (req, res) => { + res.write(validCertChain.join("\n")); +}); +const validX5uUrl = signedBaseUri + validX5uPath; + +// Setup endpoint to handle x5u lookups incorrectly. +const invalidX5uPath = "/invalid_x5u"; +const invalidCertChain = loadCertChain(testDataDir + "content_signing", [ + "aus_ee", + // This cert chain is missing the intermediate cert! +]); +signedTestServer.registerPathHandler(invalidX5uPath, (req, res) => { + res.write(invalidCertChain.join("\n")); +}); +const invalidX5uUrl = signedBaseUri + invalidX5uPath; + +// Will hold the XML data from good.xml. +let goodXml; +// This sig is generated using the following command at mozilla-central root +// `cat toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml | ./mach python security/manager/ssl/tests/unit/test_content_signing/pysign.py` +// If test certificates are regenerated, this signature must also be. +const goodXmlContentSignature = + "7QYnPqFoOlS02BpDdIRIljzmPr6BFwPs1z1y8KJUBlnU7EVG6FbnXmVVt5Op9wDzgvhXX7th8qFJvpPOZs_B_tHRDNJ8SK0HN95BAN15z3ZW2r95SSHmU-fP2JgoNOR3"; + +const goodXmlPath = "/good.xml"; +// Requests use query strings to test different signature states. +const validSignatureQuery = "validSignature"; +const invalidSignatureQuery = "invalidSignature"; +const missingSignatureQuery = "missingSignature"; +const incompleteSignatureQuery = "incompleteSignature"; +const badX5uSignatureQuery = "badX5uSignature"; +signedTestServer.registerPathHandler(goodXmlPath, (req, res) => { + if (req.queryString == validSignatureQuery) { + res.setHeader( + "content-signature", + `x5u=${validX5uUrl}; p384ecdsa=${goodXmlContentSignature}` + ); + } else if (req.queryString == invalidSignatureQuery) { + res.setHeader("content-signature", `x5u=${validX5uUrl}; p384ecdsa=garbage`); + } else if (req.queryString == missingSignatureQuery) { + // Intentionally don't set the header. + } else if (req.queryString == incompleteSignatureQuery) { + res.setHeader( + "content-signature", + `x5u=${validX5uUrl}` // There's no p384ecdsa part! + ); + } else if (req.queryString == badX5uSignatureQuery) { + res.setHeader( + "content-signature", + `x5u=${invalidX5uUrl}; p384ecdsa=${goodXmlContentSignature}` + ); + } else { + Assert.ok( + false, + "Invalid queryString passed to server! Tests shouldn't do that!" + ); + } + res.write(goodXml); +}); + +// Handle aysnc load of good.xml. +add_task(async function load_good_xml() { + goodXml = await IOUtils.readUTF8(do_get_file(testDataDir + "good.xml").path); +}); + +add_task(async function test_valid_content_signature() { + try { + const res = await ProductAddonChecker.getProductAddonList( + signedBaseUri + goodXmlPath + "?" + validSignatureQuery, + /*allowNonBuiltIn*/ false, + /*allowedCerts*/ false, + /*verifyContentSignature*/ true + ); + Assert.ok(true, "Should successfully get addon list"); + + // Smoke test the results are as expected. + Assert.equal(res.addons[0].id, "test1"); + Assert.equal(res.addons[1].id, "test2"); + Assert.equal(res.addons[2].id, "test3"); + Assert.equal(res.addons[3].id, "test4"); + Assert.equal(res.addons[4].id, undefined); + } catch (e) { + Assert.ok( + false, + `Should successfully get addon list, instead failed with ${e}` + ); + } +}); + +add_task(async function test_invalid_content_signature() { + try { + await ProductAddonChecker.getProductAddonList( + signedBaseUri + goodXmlPath + "?" + invalidSignatureQuery, + /*allowNonBuiltIn*/ false, + /*allowedCerts*/ false, + /*verifyContentSignature*/ true + ); + Assert.ok(false, "Should fail to get addon list"); + } catch (e) { + Assert.ok(true, "Should fail to get addon list"); + // The nsIContentSignatureVerifier will throw an error on this path, + // check that we've caught and re-thrown, but don't check the full error + // message as it's messy and subject to change. + Assert.ok( + e.message.startsWith("Content signature validation failed:"), + "Should get signature failure message" + ); + } +}); + +add_task(async function test_missing_content_signature_header() { + try { + await ProductAddonChecker.getProductAddonList( + signedBaseUri + goodXmlPath + "?" + missingSignatureQuery, + /*allowNonBuiltIn*/ false, + /*allowedCerts*/ false, + /*verifyContentSignature*/ true + ); + Assert.ok(false, "Should fail to get addon list"); + } catch (e) { + Assert.ok(true, "Should fail to get addon list"); + Assert.equal( + e.addonCheckerErr, + ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR + ); + Assert.equal( + e.message, + "Content signature validation failed: missing content signature header" + ); + } +}); + +add_task(async function test_incomplete_content_signature_header() { + try { + await ProductAddonChecker.getProductAddonList( + signedBaseUri + goodXmlPath + "?" + incompleteSignatureQuery, + /*allowNonBuiltIn*/ false, + /*allowedCerts*/ false, + /*verifyContentSignature*/ true + ); + Assert.ok(false, "Should fail to get addon list"); + } catch (e) { + Assert.ok(true, "Should fail to get addon list"); + Assert.equal( + e.addonCheckerErr, + ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR + ); + Assert.equal( + e.message, + "Content signature validation failed: missing signature" + ); + } +}); + +add_task(async function test_bad_x5u_content_signature_header() { + try { + await ProductAddonChecker.getProductAddonList( + signedBaseUri + goodXmlPath + "?" + badX5uSignatureQuery, + /*allowNonBuiltIn*/ false, + /*allowedCerts*/ false, + /*verifyContentSignature*/ true + ); + Assert.ok(false, "Should fail to get addon list"); + } catch (e) { + Assert.ok(true, "Should fail to get addon list"); + Assert.equal( + e.addonCheckerErr, + ProductAddonChecker.VERIFICATION_INVALID_ERR + ); + Assert.equal(e.message, "Content signature is not valid"); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_QuarantinedDomains_AMRemoteSettings.js b/toolkit/mozapps/extensions/test/xpcshell/test_QuarantinedDomains_AMRemoteSettings.js new file mode 100644 index 0000000000..8251c12964 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_QuarantinedDomains_AMRemoteSettings.js @@ -0,0 +1,79 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42"); + +const QUARANTINE_LIST_PREF = "extensions.quarantinedDomains.list"; + +async function testQuarantinedDomainsFromRemoteSettings() { + // Same as MAX_PREF_LENGTH as defined in Preferences.cpp, + // see https://searchfox.org/mozilla-central/rev/06510249/modules/libpref/Preferences.cpp#162 + const MAX_PREF_LENGTH = 1 * 1024 * 1024; + const quarantinedDomainsSets = { + testSet1: "example.com,example.org", + testSet2: "someothersite.org,testset2.org", + }; + await setAndEmitFakeRemoteSettingsData([ + { + id: "quarantinedDomains-testSet-toolong", + quarantinedDomains: { + [QUARANTINE_LIST_PREF]: "x".repeat(MAX_PREF_LENGTH + 1), + }, + }, + { + id: "quarantinedDomains-testSet1", + quarantinedDomains: { + [QUARANTINE_LIST_PREF]: quarantinedDomainsSets.testSet1, + }, + }, + { + id: "quarantinedDomains-testSet2", + quarantinedDomains: { + [QUARANTINE_LIST_PREF]: quarantinedDomainsSets.testSet2, + }, + }, + ]); + + Assert.equal( + Services.prefs.getPrefType(QUARANTINE_LIST_PREF), + Services.prefs.PREF_STRING, + `Expect ${QUARANTINE_LIST_PREF} preference type to be string` + ); + // The entry too big to fix in the pref value should throw but not preventing + // the other entries from being processed. + // The Last collection entry setting the pref wins, and so we expect + // the pref to be set to the domains listed in the collection + // entry with id "quarantinedDomains-testSet2". + Assert.equal( + Services.prefs.getStringPref(QUARANTINE_LIST_PREF), + quarantinedDomainsSets.testSet2, + `Got the expected value set on ${QUARANTINE_LIST_PREF}` + ); + + // Confirm that the updated quarantined domains list is now reflected + // by the results returned by WebExtensionPolicy.isQuarantinedURI. + // NOTE: Additional test coverage over the quarantined domains behaviors + // are part of a separate xpcshell test + // (see toolkit/components/extensions/test/xpcshell/test_QuarantinedDomains.js). + for (const domain of quarantinedDomainsSets.testSet2.split(",")) { + let uri = Services.io.newURI(`https://${domain}/`); + ok( + WebExtensionPolicy.isQuarantinedURI(uri), + `Expect ${domain} to be quarantined` + ); + } + + for (const domain of quarantinedDomainsSets.testSet1.split(",")) { + let uri = Services.io.newURI(`https://${domain}/`); + ok( + !WebExtensionPolicy.isQuarantinedURI(uri), + `Expect ${domain} to not be quarantined` + ); + } +} + +add_setup(async () => { + await AddonTestUtils.promiseStartupManager(); +}); + +add_task(testQuarantinedDomainsFromRemoteSettings); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js new file mode 100644 index 0000000000..f769f8e4fd --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js @@ -0,0 +1,133 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test that we only check manifest age for disabled extensions + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +add_task(async function setup() { + await promiseStartupManager(); + registerCleanupFunction(promiseShutdownManager); + + await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { gecko: { id: "enabled@tests.mozilla.org" } }, + }, + }); + await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { + gecko: { id: "disabled@tests.mozilla.org" }, + }, + }, + }); + + let addon = await promiseAddonByID("disabled@tests.mozilla.org"); + notEqual(addon, null); + await addon.disable(); +}); + +// Keep track of the last time stamp we've used, so that we can keep moving +// it forward (if we touch two different files in the same add-on with the same +// timestamp we may not consider the change significant) +var lastTimestamp = Date.now(); + +/* + * Helper function to touch a file and then test whether we detect the change. + * @param XS The XPIState object. + * @param aPath File path to touch. + * @param aChange True if we should notice the change, False if we shouldn't. + */ +function checkChange(XS, aPath, aChange) { + Assert.ok(aPath.exists()); + lastTimestamp += 10000; + info("Touching file " + aPath.path + " with " + lastTimestamp); + aPath.lastModifiedTime = lastTimestamp; + Assert.equal(XS.scanForChanges(), aChange); + // Save the pref so we don't detect this change again + XS.save(); +} + +// Get a reference to the XPIState (loaded by startupManager) so we can unit test it. +function getXS() { + const { XPIInternal } = ChromeUtils.import( + "resource://gre/modules/addons/XPIProvider.jsm" + ); + return XPIInternal.XPIStates; +} + +async function getXSJSON() { + await AddonTestUtils.loadAddonsList(true); + + return aomStartup.readStartupData(); +} + +add_task(async function detect_touches() { + let XS = getXS(); + + // Should be no changes detected here, because everything should start out up-to-date. + Assert.ok(!XS.scanForChanges()); + + let states = XS.getLocation("app-profile"); + + // State should correctly reflect enabled/disabled + + let state = states.get("enabled@tests.mozilla.org"); + Assert.notEqual(state, null, "Found xpi state for enabled extension"); + Assert.ok(state.enabled, "enabled extension has correct xpi state"); + + state = states.get("disabled@tests.mozilla.org"); + Assert.notEqual(state, null, "Found xpi state for disabled extension"); + Assert.ok(!state.enabled, "disabled extension has correct xpi state"); + + // Touch various files and make sure the change is detected. + + // We notice that a packed XPI is touched for an enabled add-on. + let peFile = profileDir.clone(); + peFile.append("enabled@tests.mozilla.org.xpi"); + checkChange(XS, peFile, true); + + // We should notice the packed XPI change for a disabled add-on too. + let pdFile = profileDir.clone(); + pdFile.append("disabled@tests.mozilla.org.xpi"); + checkChange(XS, pdFile, true); +}); + +/* + * Uninstalling extensions should immediately remove them from XPIStates. + */ +add_task(async function uninstall_bootstrap() { + let pe = await promiseAddonByID("enabled@tests.mozilla.org"); + await pe.uninstall(); + + let xpiState = await getXSJSON(); + Assert.equal( + false, + "enabled@tests.mozilla.org" in xpiState["app-profile"].addons + ); +}); + +/* + * Installing an extension should immediately add it to XPIState + */ +add_task(async function install_bootstrap() { + const ID = "addon@tests.mozilla.org"; + let XS = getXS(); + + await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { gecko: { id: ID } }, + }, + }); + let addon = await promiseAddonByID(ID); + + let xState = XS.getAddon("app-profile", ID); + Assert.ok(!!xState); + Assert.ok(xState.enabled); + Assert.equal(xState.mtime, addon.updateDate.getTime()); + await addon.uninstall(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_XPIcancel.js b/toolkit/mozapps/extensions/test/xpcshell/test_XPIcancel.js new file mode 100644 index 0000000000..8910d959f0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_XPIcancel.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test the cancellable doing/done/cancelAll API in XPIProvider + +const { XPIInstall } = ChromeUtils.import( + "resource://gre/modules/addons/XPIInstall.jsm" +); + +function run_test() { + // Check that cancelling with nothing in progress doesn't blow up + XPIInstall.cancelAll(); + + // Check that a basic object gets cancelled + let getsCancelled = { + isCancelled: false, + cancel() { + if (this.isCancelled) { + do_throw("Already cancelled"); + } + this.isCancelled = true; + }, + }; + XPIInstall.doing(getsCancelled); + XPIInstall.cancelAll(); + Assert.ok(getsCancelled.isCancelled); + + // Check that if we complete a cancellable, it doesn't get cancelled + let doesntGetCancelled = { + cancel: () => do_throw("This should not have been cancelled"), + }; + XPIInstall.doing(doesntGetCancelled); + Assert.ok(XPIInstall.done(doesntGetCancelled)); + XPIInstall.cancelAll(); + + // A cancellable that adds a cancellable + getsCancelled.isCancelled = false; + let addsAnother = { + isCancelled: false, + cancel() { + if (this.isCancelled) { + do_throw("Already cancelled"); + } + this.isCancelled = true; + XPIInstall.doing(getsCancelled); + }, + }; + XPIInstall.doing(addsAnother); + XPIInstall.cancelAll(); + Assert.ok(addsAnother.isCancelled); + Assert.ok(getsCancelled.isCancelled); + + // A cancellable that removes another. This assumes that Set() iterates in the + // order that members were added + let removesAnother = { + isCancelled: false, + cancel() { + if (this.isCancelled) { + do_throw("Already cancelled"); + } + this.isCancelled = true; + XPIInstall.done(doesntGetCancelled); + }, + }; + XPIInstall.doing(removesAnother); + XPIInstall.doing(doesntGetCancelled); + XPIInstall.cancelAll(); + Assert.ok(removesAnother.isCancelled); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_addonStartup.js b/toolkit/mozapps/extensions/test/xpcshell/test_addonStartup.js new file mode 100644 index 0000000000..715b74a068 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_addonStartup.js @@ -0,0 +1,93 @@ +"use strict"; + +add_task(async function test_XPIStates_invalid_paths() { + let { path } = gAddonStartup; + + let startupDatasets = [ + { + "app-profile": { + addons: { + "xpcshell-something-or-other@mozilla.org": { + bootstrapped: true, + dependencies: [], + enabled: true, + hasEmbeddedWebExtension: false, + lastModifiedTime: 1, + path: "xpcshell-something-or-other@mozilla.org", + version: "0.0.0", + }, + }, + checkStartupModifications: true, + path: "/home/xpcshell/.mozilla/firefox/default/extensions", + }, + }, + { + "app-profile": { + addons: { + "xpcshell-something-or-other@mozilla.org": { + bootstrapped: true, + dependencies: [], + enabled: true, + hasEmbeddedWebExtension: false, + lastModifiedTime: 1, + path: "xpcshell-something-or-other@mozilla.org", + version: "0.0.0", + }, + }, + checkStartupModifications: true, + path: "c:\\Users\\XpcShell\\Application Data\\Mozilla Firefox\\Profiles\\meh", + }, + }, + { + "app-profile": { + addons: { + "xpcshell-something-or-other@mozilla.org": { + bootstrapped: true, + dependencies: [], + enabled: true, + hasEmbeddedWebExtension: false, + lastModifiedTime: 1, + path: "/home/xpcshell/my-extensions/something-or-other", + version: "0.0.0", + }, + }, + checkStartupModifications: true, + path: "/home/xpcshell/.mozilla/firefox/default/extensions", + }, + }, + { + "app-profile": { + addons: { + "xpcshell-something-or-other@mozilla.org": { + bootstrapped: true, + dependencies: [], + enabled: true, + hasEmbeddedWebExtension: false, + lastModifiedTime: 1, + path: "c:\\Users\\XpcShell\\my-extensions\\something-or-other", + version: "0.0.0", + }, + }, + checkStartupModifications: true, + path: "c:\\Users\\XpcShell\\Application Data\\Mozilla Firefox\\Profiles\\meh", + }, + }, + ]; + + for (let startupData of startupDatasets) { + await IOUtils.writeJSON(path, startupData, { compress: true }); + + try { + let result = aomStartup.readStartupData(); + info(`readStartupData() returned ${JSON.stringify(result)}`); + } catch (e) { + // We don't care if this throws, only that it doesn't crash. + info(`readStartupData() threw: ${e}`); + equal( + e.result, + Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH, + "Got expected error code" + ); + } + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_addon_manager_telemetry_events.js b/toolkit/mozapps/extensions/test/xpcshell/test_addon_manager_telemetry_events.js new file mode 100644 index 0000000000..9c8565a0bc --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_addon_manager_telemetry_events.js @@ -0,0 +1,875 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { AMTelemetry } = ChromeUtils.importESModule( + "resource://gre/modules/AddonManager.sys.mjs" +); + +// We don't have an easy way to serve update manifests from a secure URL. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +const EVENT_CATEGORY = "addonsManager"; +const EVENT_METHODS_INSTALL = ["install", "update"]; +const EVENT_METHODS_MANAGE = ["disable", "enable", "uninstall"]; +const EVENT_METHODS = [...EVENT_METHODS_INSTALL, ...EVENT_METHODS_MANAGE]; + +const FAKE_INSTALL_TELEMETRY_INFO = { + source: "fake-install-source", + method: "fake-install-method", +}; + +function getTelemetryEvents(includeMethods = EVENT_METHODS) { + const snapshot = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS, + true + ); + + ok( + snapshot.parent && !!snapshot.parent.length, + "Got parent telemetry events in the snapshot" + ); + + return snapshot.parent + .filter(([timestamp, category, method]) => { + const includeMethod = includeMethods + ? includeMethods.includes(method) + : true; + + return category === EVENT_CATEGORY && includeMethod; + }) + .map(event => { + return { + method: event[2], + object: event[3], + value: event[4], + extra: event[5], + }; + }); +} + +function assertNoTelemetryEvents() { + const snapshot = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS, + true + ); + + if (!snapshot.parent || snapshot.parent.length === 0) { + ok(true, "Got no parent telemetry events as expected"); + return; + } + + let filteredEvents = snapshot.parent.filter( + ([timestamp, category, method]) => { + return category === EVENT_CATEGORY; + } + ); + + Assert.deepEqual(filteredEvents, [], "Got no AMTelemetry events as expected"); +} + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + // Thunderbird doesn't have one or more of the probes used in this test. + // Ensure the data is collected anyway. + Services.prefs.setBoolPref( + "toolkit.telemetry.testing.overrideProductsCheck", + true + ); + + // Telemetry test setup needed to ensure that the builtin events are defined + // and they can be collected and verified. + await TelemetryController.testSetup(); + + await promiseStartupManager(); +}); + +// Test the basic install and management flows. +add_task( + { + // We need to enable this pref because some assertions verify that + // `installOrigins` is collected in some Telemetry events. + pref_set: [["extensions.install_origins.enabled", true]], + }, + async function test_basic_telemetry_events() { + const EXTENSION_ID = "basic@test.extension"; + + const manifest = { + browser_specific_settings: { gecko: { id: EXTENSION_ID } }, + }; + + let extension = ExtensionTestUtils.loadExtension({ + manifest, + useAddonManager: "permanent", + amInstallTelemetryInfo: FAKE_INSTALL_TELEMETRY_INFO, + }); + + await extension.startup(); + + const addon = await promiseAddonByID(EXTENSION_ID); + + info("Disabling the extension"); + await addon.disable(); + + info("Set pending uninstall on the extension"); + const onceAddonUninstalling = promiseAddonEvent("onUninstalling"); + addon.uninstall(true); + await onceAddonUninstalling; + + info("Cancel pending uninstall"); + const oncePendingUninstallCancelled = promiseAddonEvent( + "onOperationCancelled" + ); + addon.cancelUninstall(); + await oncePendingUninstallCancelled; + + info("Re-enabling the extension"); + const onceAddonStarted = promiseWebExtensionStartup(EXTENSION_ID); + const onceAddonEnabled = promiseAddonEvent("onEnabled"); + addon.enable(); + await Promise.all([onceAddonEnabled, onceAddonStarted]); + + await extension.unload(); + + let amEvents = getTelemetryEvents(); + + const amMethods = amEvents.map(evt => evt.method); + const expectedMethods = [ + // These two install methods are related to the steps "started" and "completed". + "install", + "install", + // Sequence of disable and enable (pending uninstall and undo uninstall are not going to + // record any telemetry events). + "disable", + "enable", + // The final "uninstall" when the test extension is unloaded. + "uninstall", + ]; + Assert.deepEqual( + amMethods, + expectedMethods, + "Got the addonsManager telemetry events in the expected order" + ); + + const installEvents = amEvents.filter(evt => evt.method === "install"); + const expectedInstallEvents = [ + { + method: "install", + object: "extension", + value: "1", + extra: { + addon_id: "basic@test.extension", + step: "started", + install_origins: "0", + ...FAKE_INSTALL_TELEMETRY_INFO, + }, + }, + { + method: "install", + object: "extension", + value: "1", + extra: { + addon_id: "basic@test.extension", + step: "completed", + install_origins: "0", + ...FAKE_INSTALL_TELEMETRY_INFO, + }, + }, + ]; + Assert.deepEqual( + installEvents, + expectedInstallEvents, + "Got the expected addonsManager.install events" + ); + + const manageEvents = amEvents.filter(evt => + EVENT_METHODS_MANAGE.includes(evt.method) + ); + const expectedExtra = FAKE_INSTALL_TELEMETRY_INFO; + const expectedManageEvents = [ + { + method: "disable", + object: "extension", + value: "basic@test.extension", + extra: expectedExtra, + }, + { + method: "enable", + object: "extension", + value: "basic@test.extension", + extra: expectedExtra, + }, + { + method: "uninstall", + object: "extension", + value: "basic@test.extension", + extra: expectedExtra, + }, + ]; + Assert.deepEqual( + manageEvents, + expectedManageEvents, + "Got the expected addonsManager.manage events" + ); + + // Verify that on every install flow, the value of the addonsManager.install Telemetry events + // is being incremented. + + extension = ExtensionTestUtils.loadExtension({ + manifest, + useAddonManager: "permanent", + amInstallTelemetryInfo: FAKE_INSTALL_TELEMETRY_INFO, + }); + + await extension.startup(); + await extension.unload(); + + const eventsFromNewInstall = getTelemetryEvents(); + equal( + eventsFromNewInstall.length, + 3, + "Got the expected number of addonsManager install events" + ); + + const eventValues = eventsFromNewInstall + .filter(evt => evt.method === "install") + .map(evt => evt.value); + const expectedValues = ["2", "2"]; + Assert.deepEqual( + eventValues, + expectedValues, + "Got the expected install id" + ); + } +); + +add_task( + { + // We need to enable this pref because some assertions verify that + // `installOrigins` is collected in some Telemetry events. + pref_set: [["extensions.install_origins.enabled", true]], + }, + async function test_update_telemetry_events() { + const EXTENSION_ID = "basic@test.extension"; + + const testserver = AddonTestUtils.createHttpServer({ + hosts: ["example.com"], + }); + + const updateUrl = `http://example.com/updates.json`; + + const testAddon = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: EXTENSION_ID, + update_url: updateUrl, + }, + }, + }, + }); + + const testUserRequestedUpdate = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "2.0", + browser_specific_settings: { + gecko: { + id: EXTENSION_ID, + update_url: updateUrl, + }, + }, + }, + }); + const testAppRequestedUpdate = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "2.1", + browser_specific_settings: { + gecko: { + id: EXTENSION_ID, + update_url: updateUrl, + }, + }, + }, + }); + + testserver.registerFile( + `/addons/${EXTENSION_ID}-2.0.xpi`, + testUserRequestedUpdate + ); + testserver.registerFile( + `/addons/${EXTENSION_ID}-2.1.xpi`, + testAppRequestedUpdate + ); + + let updates = [ + { + version: "2.0", + update_link: `http://example.com/addons/${EXTENSION_ID}-2.0.xpi`, + applications: { + gecko: { + strict_min_version: "1", + }, + }, + }, + ]; + + testserver.registerPathHandler("/updates.json", (request, response) => { + response.write(`{ + "addons": { + "${EXTENSION_ID}": { + "updates": ${JSON.stringify(updates)} + } + } + }`); + }); + + await promiseInstallFile(testAddon, false, FAKE_INSTALL_TELEMETRY_INFO); + + let addon = await AddonManager.getAddonByID(EXTENSION_ID); + + // User requested update. + await promiseFindAddonUpdates( + addon, + AddonManager.UPDATE_WHEN_USER_REQUESTED + ); + let installs = await AddonManager.getAllInstalls(); + await promiseCompleteAllInstalls(installs); + + updates = [ + { + version: "2.1", + update_link: `http://example.com/addons/${EXTENSION_ID}-2.1.xpi`, + applications: { + gecko: { + strict_min_version: "1", + }, + }, + }, + ]; + + // App requested update. + await promiseFindAddonUpdates( + addon, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + let installs2 = await AddonManager.getAllInstalls(); + await promiseCompleteAllInstalls(installs2); + + updates = [ + { + version: "2.1.1", + update_link: `http://example.com/addons/${EXTENSION_ID}-2.1.1-network-failure.xpi`, + applications: { + gecko: { + strict_min_version: "1", + }, + }, + }, + ]; + + // Update which fails to download. + await promiseFindAddonUpdates( + addon, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + let installs3 = await AddonManager.getAllInstalls(); + await promiseCompleteAllInstalls(installs3); + + let amEvents = getTelemetryEvents(); + + const installEvents = amEvents + .filter(evt => evt.method === "install") + .map(evt => { + delete evt.value; + return evt; + }); + + const addon_id = "basic@test.extension"; + const object = "extension"; + + Assert.deepEqual( + installEvents, + [ + { + method: "install", + object, + extra: { + addon_id, + step: "started", + install_origins: "0", + ...FAKE_INSTALL_TELEMETRY_INFO, + }, + }, + { + method: "install", + object, + extra: { + addon_id, + step: "completed", + install_origins: "0", + ...FAKE_INSTALL_TELEMETRY_INFO, + }, + }, + ], + "Got the expected addonsManager.install events" + ); + + const updateEvents = amEvents + .filter(evt => evt.method === "update") + .map(evt => { + delete evt.value; + return evt; + }); + + const method = "update"; + const baseExtra = FAKE_INSTALL_TELEMETRY_INFO; + + const expectedUpdateEvents = [ + // User-requested update to the 2.1 version. + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "started", + updated_from: "user", + }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "download_started", + updated_from: "user", + }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "download_completed", + updated_from: "user", + }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "completed", + updated_from: "user", + }, + }, + // App-requested update to the 2.1 version. + { + method, + object, + extra: { ...baseExtra, addon_id, step: "started", updated_from: "app" }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "download_started", + updated_from: "app", + }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "download_completed", + updated_from: "app", + }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "completed", + updated_from: "app", + }, + }, + // Broken update to the 2.1.1 version (on ERROR_NETWORK_FAILURE). + { + method, + object, + extra: { ...baseExtra, addon_id, step: "started", updated_from: "app" }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + step: "download_started", + updated_from: "app", + }, + }, + { + method, + object, + extra: { + ...baseExtra, + addon_id, + error: "ERROR_NETWORK_FAILURE", + step: "download_failed", + updated_from: "app", + }, + }, + ]; + + for (let i = 0; i < updateEvents.length; i++) { + if ( + ["download_completed", "download_failed"].includes( + updateEvents[i].extra.step + ) + ) { + const download_time = parseInt(updateEvents[i].extra.download_time, 10); + ok( + !isNaN(download_time) && download_time > 0, + `Got a download_time extra in ${updateEvents[i].extra.step} events: ${download_time}` + ); + + delete updateEvents[i].extra.download_time; + } + + Assert.deepEqual( + updateEvents[i], + expectedUpdateEvents[i], + "Got the expected addonsManager.update events" + ); + } + + equal( + updateEvents.length, + expectedUpdateEvents.length, + "Got the expected number of addonsManager.update events" + ); + + await addon.uninstall(); + + // Clear any AMTelemetry events related to the uninstalled extensions. + getTelemetryEvents(); + } +); + +add_task(async function test_no_telemetry_events_on_internal_sources() { + assertNoTelemetryEvents(); + + const INTERNAL_EXTENSION_ID = "internal@test.extension"; + + // Install an extension which has internal as its installation source, + // and expect it to do not appear in the collected telemetry events. + let internalExtension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_specific_settings: { gecko: { id: INTERNAL_EXTENSION_ID } }, + }, + useAddonManager: "permanent", + amInstallTelemetryInfo: { source: "internal" }, + }); + + await internalExtension.startup(); + + const internalAddon = await promiseAddonByID(INTERNAL_EXTENSION_ID); + + info("Disabling the internal extension"); + const onceInternalAddonDisabled = promiseAddonEvent("onDisabled"); + internalAddon.disable(); + await onceInternalAddonDisabled; + + info("Re-enabling the internal extension"); + const onceInternalAddonStarted = promiseWebExtensionStartup( + INTERNAL_EXTENSION_ID + ); + const onceInternalAddonEnabled = promiseAddonEvent("onEnabled"); + internalAddon.enable(); + await Promise.all([onceInternalAddonEnabled, onceInternalAddonStarted]); + + await internalExtension.unload(); + + assertNoTelemetryEvents(); +}); + +add_task(async function test_collect_attribution_data_for_amo() { + assertNoTelemetryEvents(); + + // We pass the `source` value to `amInstallTelemetryInfo` in this test so the + // host could be anything in this variable below. Whether to collect + // attribution data for AMO is determined by the `source` value, not this + // host. + const url = "https://addons.mozilla.org/"; + const addonId = "{28374a9a-676c-5640-bfa7-865cd4686ead}"; + // This is the SHA256 hash of the `addonId` above. + const expectedHashedAddonId = + "cf815c9f45c249473d630705f89e64d359737a106a375bbb83be71e6d52dc234"; + + for (const { source, sourceURL, expectNoEvent, expectedAmoAttribution } of [ + // Basic test. + { + source: "amo", + sourceURL: `${url}?utm_content=utm-content-value`, + expectedAmoAttribution: { + utm_content: "utm-content-value", + }, + }, + // No UTM parameters will produce an event without any attribution data. + { + source: "amo", + sourceURL: url, + expectedAmoAttribution: {}, + }, + // Invalid source URLs will produce an event without any attribution data. + { + source: "amo", + sourceURL: "invalid-url", + expectedAmoAttribution: {}, + }, + // No source URL. + { + source: "amo", + sourceURL: null, + expectedAmoAttribution: {}, + }, + { + source: "amo", + sourceURL: undefined, + expectedAmoAttribution: {}, + }, + // Ignore unsupported/bogus UTM parameters. + { + source: "amo", + sourceURL: [ + `${url}?utm_content=utm-content-value`, + "utm_foo=invalid", + "utm_campaign=some-campaign", + "utm_term=invalid-too", + ].join("&"), + expectedAmoAttribution: { + utm_campaign: "some-campaign", + utm_content: "utm-content-value", + }, + }, + { + source: "amo", + sourceURL: `${url}?foo=bar&q=azerty`, + expectedAmoAttribution: {}, + }, + // Long values are truncated. + { + source: "amo", + sourceURL: `${url}?utm_medium=${"a".repeat(100)}`, + expectedAmoAttribution: { + utm_medium: "a".repeat(40), + }, + }, + // Only collect the first value if the parameter is passed more than once. + { + source: "amo", + sourceURL: `${url}?utm_source=first-source&utm_source=second-source`, + expectedAmoAttribution: { + utm_source: "first-source", + }, + }, + // When source is "disco", we don't collect the UTM parameters. + { + source: "disco", + sourceURL: `${url}?utm_content=utm-content-value`, + expectedAmoAttribution: {}, + }, + // When source is neither "amo" nor "disco", we don't collect anything. + { + source: "link", + sourceURL: `${url}?utm_content=utm-content-value`, + expectNoEvent: true, + }, + { + source: null, + sourceURL: `${url}?utm_content=utm-content-value`, + expectNoEvent: true, + }, + { + source: undefined, + sourceURL: `${url}?utm_content=utm-content-value`, + expectNoEvent: true, + }, + ]) { + const extDefinition = { + useAddonManager: "permanent", + manifest: { + version: "1.0", + browser_specific_settings: { gecko: { id: addonId } }, + }, + amInstallTelemetryInfo: { + ...FAKE_INSTALL_TELEMETRY_INFO, + sourceURL, + source, + }, + }; + let extension = ExtensionTestUtils.loadExtension(extDefinition); + + await extension.startup(); + + const installStatsEvents = getTelemetryEvents(["install_stats"]); + + if (expectNoEvent === true) { + Assert.equal( + installStatsEvents.length, + 0, + "no install_stats event should be recorded" + ); + } else { + Assert.equal( + installStatsEvents.length, + 1, + "only one install_stats event should be recorded" + ); + + const installStatsEvent = installStatsEvents[0]; + + Assert.deepEqual(installStatsEvent, { + method: "install_stats", + object: "extension", + value: expectedHashedAddonId, + extra: { + addon_id: addonId, + ...expectedAmoAttribution, + }, + }); + } + + await extension.upgrade({ + ...extDefinition, + manifest: { + ...extDefinition.manifest, + version: "2.0", + }, + }); + + Assert.deepEqual( + getTelemetryEvents(["install_stats"]), + [], + "no install_stats event should be recorded on addon updates" + ); + + await extension.unload(); + } + + getTelemetryEvents(); +}); + +add_task(async function test_collect_attribution_data_for_amo_with_long_id() { + assertNoTelemetryEvents(); + + // We pass the `source` value to `installTelemetryInfo` in this test so the + // host could be anything in this variable below. Whether to collect + // attribution data for AMO is determined by the `source` value, not this + // host. + const url = "https://addons.mozilla.org/"; + const addonId = `@${"a".repeat(90)}`; + // This is the SHA256 hash of the `addonId` above. + const expectedHashedAddonId = + "964d902353fc1c127228b66ec8a174c340cb2e02dbb550c6000fb1cd3ca2f489"; + + const installTelemetryInfo = { + ...FAKE_INSTALL_TELEMETRY_INFO, + sourceURL: `${url}?utm_content=utm-content-value`, + source: "amo", + }; + + // We call `recordInstallStatsEvent()` directly because using an add-on ID + // longer than 64 chars causes signing issues in tests (because of the + // differences between the fake CertDB injected by + // `AddonTestUtils.overrideCertDB()` and the real one). + const fakeAddonInstall = { + addon: { id: addonId }, + type: "extension", + installTelemetryInfo, + hashedAddonId: expectedHashedAddonId, + }; + AMTelemetry.recordInstallStatsEvent(fakeAddonInstall); + + const installStatsEvents = getTelemetryEvents(["install_stats"]); + Assert.equal( + installStatsEvents.length, + 1, + "only one install_stats event should be recorded" + ); + + const installStatsEvent = installStatsEvents[0]; + + Assert.deepEqual(installStatsEvent, { + method: "install_stats", + object: "extension", + value: expectedHashedAddonId, + extra: { + addon_id: AMTelemetry.getTrimmedString(addonId), + utm_content: "utm-content-value", + }, + }); +}); + +add_task(async function test_collect_attribution_data_for_rtamo() { + assertNoTelemetryEvents(); + + const url = "https://addons.mozilla.org/"; + const addonId = "{28374a9a-676c-5640-bfa7-865cd4686ead}"; + // This is the SHA256 hash of the `addonId` above. + const expectedHashedAddonId = + "cf815c9f45c249473d630705f89e64d359737a106a375bbb83be71e6d52dc234"; + + // We simulate what is happening in: + // https://searchfox.org/mozilla-central/rev/d2786d9a6af7507bc3443023f0495b36b7e84c2d/browser/components/newtab/content-src/lib/aboutwelcome-utils.js#91 + const installTelemetryInfo = { + ...FAKE_INSTALL_TELEMETRY_INFO, + sourceURL: `${url}?utm_content=utm-content-value`, + source: "rtamo", + }; + + const fakeAddonInstall = { + addon: { id: addonId }, + type: "extension", + installTelemetryInfo, + hashedAddonId: expectedHashedAddonId, + }; + AMTelemetry.recordInstallStatsEvent(fakeAddonInstall); + + const installStatsEvents = getTelemetryEvents(["install_stats"]); + Assert.equal( + installStatsEvents.length, + 1, + "only one install_stats event should be recorded" + ); + + const installStatsEvent = installStatsEvents[0]; + + Assert.deepEqual(installStatsEvent, { + method: "install_stats", + object: "extension", + value: expectedHashedAddonId, + extra: { + addon_id: AMTelemetry.getTrimmedString(addonId), + }, + }); +}); + +add_task(async function teardown() { + await TelemetryController.testShutdown(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_amo_stats_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/test_amo_stats_telemetry.js new file mode 100644 index 0000000000..8176c5881a --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_amo_stats_telemetry.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "42" +); + +add_task(async function setup() { + // We need to set this pref to `true` in order to collect add-ons Telemetry + // data (which is considered extended data and disabled in CI). + const overridePreReleasePref = "toolkit.telemetry.testing.overridePreRelease"; + let oldOverrideValue = Services.prefs.getBoolPref( + overridePreReleasePref, + false + ); + Services.prefs.setBoolPref(overridePreReleasePref, true); + registerCleanupFunction(() => { + Services.prefs.setBoolPref(overridePreReleasePref, oldOverrideValue); + }); + + await TelemetryController.testSetup(); + await AddonTestUtils.promiseStartupManager(); +}); + +add_task(async function test_ping_payload_and_environment() { + const extensions = [ + { + id: "addons-telemetry@test-extension-1", + name: "some extension 1", + version: "1.2.3", + }, + { + id: "addons-telemetry@test-extension-2", + name: "some extension 2", + version: "0.1", + }, + ]; + + // Install some extensions. + const installedExtensions = []; + for (const { id, name, version } of extensions) { + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + name, + version, + browser_specific_settings: { gecko: { id } }, + }, + useAddonManager: "permanent", + }); + installedExtensions.push(extension); + + await extension.startup(); + } + + const { payload, environment } = TelemetryController.getCurrentPingData(); + + // Important: `payload.info.addons` is being used for AMO usage stats. + Assert.ok("addons" in payload.info, "payload.info.addons is defined"); + Assert.equal( + payload.info.addons, + extensions + .map(({ id, version }) => `${encodeURIComponent(id)}:${version}`) + .join(",") + ); + Assert.ok( + "XPI" in payload.addonDetails, + "payload.addonDetails.XPI is defined" + ); + for (const { id, name } of extensions) { + Assert.ok(id in payload.addonDetails.XPI); + Assert.equal(payload.addonDetails.XPI[id].name, name); + } + + const { addons } = environment; + Assert.ok( + "activeAddons" in addons, + "environment.addons.activeAddons is defined" + ); + Assert.ok("theme" in addons, "environment.addons.theme is defined"); + for (const { id } of extensions) { + Assert.ok(id in environment.addons.activeAddons); + } + + for (const extension of installedExtensions) { + await extension.unload(); + } +}); + +add_task(async function cleanup() { + await TelemetryController.testShutdown(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_aom_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_aom_startup.js new file mode 100644 index 0000000000..f59a14dbbc --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_aom_startup.js @@ -0,0 +1,189 @@ +"use strict"; + +const { JSONFile } = ChromeUtils.importESModule( + "resource://gre/modules/JSONFile.sys.mjs" +); + +const aomStartup = Cc["@mozilla.org/addons/addon-manager-startup;1"].getService( + Ci.amIAddonManagerStartup +); + +const gProfDir = do_get_profile(); + +Services.prefs.setIntPref( + "extensions.enabledScopes", + AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION +); +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0", "42.0"); + +const DUMMY_ID = "@dummy"; +const DUMMY_ADDONS = { + addons: { + "@dummy": { + lastModifiedTime: 1337, + rootURI: "resource:///modules/themes/dummy/", + version: "1", + }, + }, +}; + +const TEST_ADDON_ID = "@test-theme"; +const TEST_THEME = { + lastModifiedTime: 1337, + rootURI: "resource:///modules/themes/test/", + version: "1", +}; + +const TEST_ADDONS = { + addons: { + "@test-theme": TEST_THEME, + }, +}; + +// Utility to write out various addonStartup.json files. +async function writeAOMStartupData(data) { + let jsonFile = new JSONFile({ + path: PathUtils.join(gProfDir.path, "addonStartup.json.lz4"), + compression: "lz4", + }); + jsonFile.data = data; + await jsonFile._save(); + return aomStartup.readStartupData(); +} + +// This tests that any buitin removed from the build will +// get removed from the state data. +add_task(async function test_startup_missing_builtin() { + let startupData = await writeAOMStartupData({ + "app-builtin": DUMMY_ADDONS, + }); + Assert.ok( + !!startupData["app-builtin"].addons[DUMMY_ID], + "non-existant addon is in startup data" + ); + + await AddonTestUtils.promiseStartupManager(); + await AddonTestUtils.promiseShutdownManager(); + + // This data is flushed on shutdown, so we check it after shutdown. + startupData = aomStartup.readStartupData(); + Assert.equal( + startupData["app-builtin"].addons[DUMMY_ID], + undefined, + "non-existant addon is removed from startup data" + ); +}); + +// This test verifies that a builtin installed prior to the +// second scan is not overwritten by old state data during +// the scan. +add_task(async function test_startup_default_theme_moved() { + let startupData = await writeAOMStartupData({ + "app-profile": DUMMY_ADDONS, + "app-builtin": TEST_ADDONS, + }); + Assert.ok( + !!startupData["app-profile"].addons[DUMMY_ID], + "non-existant addon is in startup data" + ); + Assert.ok( + !!startupData["app-builtin"].addons[TEST_ADDON_ID], + "test addon is in startup data" + ); + + let themeDef = { + manifest: { + browser_specific_settings: { gecko: { id: TEST_ADDON_ID } }, + version: "1.1", + theme: {}, + }, + }; + + await setupBuiltinExtension(themeDef, "second-loc"); + await AddonTestUtils.promiseStartupManager("44"); + await AddonManager.maybeInstallBuiltinAddon( + TEST_ADDON_ID, + "1.1", + "resource://second-loc/" + ); + await AddonManagerPrivate.getNewSideloads(); + + let addon = await AddonManager.getAddonByID(TEST_ADDON_ID); + Assert.ok(!addon.foreignInstall, "addon was not marked as a foreign install"); + Assert.equal(addon.version, "1.1", "addon version is correct"); + + await AddonTestUtils.promiseShutdownManager(); + + // This data is flushed on shutdown, so we check it after shutdown. + startupData = aomStartup.readStartupData(); + Assert.equal( + startupData["app-builtin"].addons[TEST_ADDON_ID].version, + "1.1", + "startup data is correct in cache" + ); + Assert.equal( + startupData["app-builtin"].addons[DUMMY_ID], + undefined, + "non-existant addon is removed from startup data" + ); +}); + +// This test verifies that a builtin addon being updated +// is not marked as a foreignInstall. +add_task(async function test_startup_builtin_not_foreign() { + let startupData = await writeAOMStartupData({ + "app-profile": DUMMY_ADDONS, + "app-builtin": { + addons: { + "@test-theme": { + ...TEST_THEME, + rootURI: "resource://second-loc/", + }, + }, + }, + }); + Assert.ok( + !!startupData["app-profile"].addons[DUMMY_ID], + "non-existant addon is in startup data" + ); + Assert.ok( + !!startupData["app-builtin"].addons[TEST_ADDON_ID], + "test addon is in startup data" + ); + + let themeDef = { + manifest: { + browser_specific_settings: { gecko: { id: TEST_ADDON_ID } }, + version: "1.1", + theme: {}, + }, + }; + + await setupBuiltinExtension(themeDef, "second-loc"); + await AddonTestUtils.promiseStartupManager("43"); + await AddonManager.maybeInstallBuiltinAddon( + TEST_ADDON_ID, + "1.1", + "resource://second-loc/" + ); + await AddonManagerPrivate.getNewSideloads(); + + let addon = await AddonManager.getAddonByID(TEST_ADDON_ID); + Assert.ok(!addon.foreignInstall, "addon was not marked as a foreign install"); + Assert.equal(addon.version, "1.1", "addon version is correct"); + + await AddonTestUtils.promiseShutdownManager(); + + // This data is flushed on shutdown, so we check it after shutdown. + startupData = aomStartup.readStartupData(); + Assert.equal( + startupData["app-builtin"].addons[TEST_ADDON_ID].version, + "1.1", + "startup data is correct in cache" + ); + Assert.equal( + startupData["app-builtin"].addons[DUMMY_ID], + undefined, + "non-existant addon is removed from startup data" + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js b/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js new file mode 100644 index 0000000000..ec6c30bd52 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that we rebuild the database correctly if it contains +// JSON data that parses correctly but doesn't contain required fields + +add_task(async function () { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + await promiseStartupManager(); + + const ID = "addon@tests.mozilla.org"; + await promiseInstallWebExtension({ + manifest: { + version: "2.0", + browser_specific_settings: { gecko: { id: ID } }, + }, + }); + + await promiseShutdownManager(); + + // First startup/shutdown finished + // Replace the JSON store with something bogus + await IOUtils.writeJSON(gExtensionsJSON.path, { + not: "what we expect to find", + }); + + await promiseStartupManager(); + // Retrieve an addon to force the database to rebuild + let addon = await AddonManager.getAddonByID(ID); + + Assert.equal(addon.id, ID); + + await promiseShutdownManager(); + + // Make sure our JSON database has schemaVersion and our installed extension + let data = await IOUtils.readJSON(gExtensionsJSON.path); + Assert.ok("schemaVersion" in data); + Assert.equal(data.addons[0].id, ID); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js new file mode 100644 index 0000000000..0fc810bf91 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js @@ -0,0 +1,237 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we rebuild something sensible from a database with a bad schema + +var testserver = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); + +// register files with server +testserver.registerDirectory("/data/", do_get_file("data")); + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +const ADDONS = { + "addon1@tests.mozilla.org": { + manifest: { + name: "Test 1", + browser_specific_settings: { + gecko: { + id: "addon1@tests.mozilla.org", + strict_min_version: "2", + strict_max_version: "2", + }, + }, + }, + desiredValues: { + isActive: true, + userDisabled: false, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "addon2@tests.mozilla.org": { + manifest: { + name: "Test 2", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon2@tests.mozilla.org", + }, + }, + }, + initialState: { + userDisabled: true, + }, + desiredValues: { + isActive: false, + userDisabled: true, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "addon3@tests.mozilla.org": { + manifest: { + name: "Test 3", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon3@tests.mozilla.org", + update_url: "http://example.com/data/test_corrupt.json", + strict_min_version: "1", + strict_max_version: "1", + }, + }, + }, + findUpdates: true, + desiredValues: { + isActive: true, + userDisabled: false, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "addon4@tests.mozilla.org": { + manifest: { + name: "Test 4", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon4@tests.mozilla.org", + update_url: "http://example.com/data/test_corrupt.json", + strict_min_version: "1", + strict_max_version: "1", + }, + }, + }, + initialState: { + userDisabled: true, + }, + findUpdates: true, + desiredValues: { + isActive: false, + userDisabled: true, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "addon5@tests.mozilla.org": { + manifest: { + name: "Test 5", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon5@tests.mozilla.org", + strict_min_version: "1", + strict_max_version: "1", + }, + }, + }, + desiredValues: { + isActive: false, + userDisabled: false, + appDisabled: true, + pendingOperations: 0, + }, + }, + + "theme1@tests.mozilla.org": { + manifest: { + manifest_version: 2, + name: "Theme 1", + version: "1.0", + theme: { images: { theme_frame: "example.png" } }, + browser_specific_settings: { + gecko: { + id: "theme1@tests.mozilla.org", + }, + }, + }, + desiredValues: { + isActive: false, + userDisabled: true, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "theme2@tests.mozilla.org": { + manifest: { + manifest_version: 2, + name: "Theme 2", + version: "1.0", + theme: { images: { theme_frame: "example.png" } }, + browser_specific_settings: { + gecko: { + id: "theme2@tests.mozilla.org", + }, + }, + }, + initialState: { + userDisabled: false, + }, + desiredValues: { + isActive: true, + userDisabled: false, + appDisabled: false, + pendingOperations: 0, + }, + }, +}; + +const IDS = Object.keys(ADDONS); + +function promiseUpdates(addon) { + return new Promise(resolve => { + addon.findUpdates( + { onUpdateFinished: resolve }, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + }); +} + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2"); + + for (let addon of Object.values(ADDONS)) { + let webext = createTempWebExtensionFile({ manifest: addon.manifest }); + await AddonTestUtils.manuallyInstall(webext); + } + + await promiseStartupManager(); + + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + if (addon.initialState) { + await setInitialState(addons.get(id), addon.initialState); + } + if (addon.findUpdates) { + await promiseUpdates(addons.get(id)); + } + } +}); + +add_task(async function test_after_restart() { + await promiseRestartManager(); + + info("Test add-on state after restart"); + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + checkAddon(id, addons.get(id), addon.desiredValues); + } + + await promiseShutdownManager(); +}); + +add_task(async function test_after_schema_version_change() { + // After restarting the database won't be open so we can alter + // the schema + await changeXPIDBVersion(100); + + await promiseStartupManager(); + + info("Test add-on state after schema version change"); + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + checkAddon(id, addons.get(id), addon.desiredValues); + } + + await promiseShutdownManager(); +}); + +add_task(async function test_after_second_restart() { + await promiseStartupManager(); + + info("Test add-on state after second restart"); + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + checkAddon(id, addons.get(id), addon.desiredValues); + } + + await promiseShutdownManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js new file mode 100644 index 0000000000..261ef61807 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js @@ -0,0 +1,194 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This test is currently dead code. +/* eslint-disable */ + +// Tests that trying to upgrade or uninstall an extension that has a file locked +// will roll back the upgrade or uninstall and retry at the next restart + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +const ADDONS = [ + { + "install.rdf": { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Bug 587088 Test", + targetApplications: [ + { + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1", + }, + ], + }, + testfile: "", + testfile1: "", + }, + + { + "install.rdf": { + id: "addon1@tests.mozilla.org", + version: "2.0", + name: "Bug 587088 Test", + targetApplications: [ + { + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1", + }, + ], + }, + testfile: "", + testfile2: "", + }, +]; + +add_task(async function setup() { + // This is only an issue on windows. + if (!("nsIWindowsRegKey" in Ci)) return; + + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +}); + +function check_addon(aAddon, aVersion) { + Assert.notEqual(aAddon, null); + Assert.equal(aAddon.version, aVersion); + Assert.ok(aAddon.isActive); + Assert.ok(isExtensionInAddonsList(profileDir, aAddon.id)); + + Assert.ok(aAddon.hasResource("testfile")); + if (aVersion == "1.0") { + Assert.ok(aAddon.hasResource("testfile1")); + Assert.ok(!aAddon.hasResource("testfile2")); + } else { + Assert.ok(!aAddon.hasResource("testfile1")); + Assert.ok(aAddon.hasResource("testfile2")); + } + + Assert.equal(aAddon.pendingOperations, AddonManager.PENDING_NONE); +} + +function check_addon_upgrading(aAddon) { + Assert.notEqual(aAddon, null); + Assert.equal(aAddon.version, "1.0"); + Assert.ok(aAddon.isActive); + Assert.ok(isExtensionInAddonsList(profileDir, aAddon.id)); + + Assert.ok(aAddon.hasResource("testfile")); + Assert.ok(aAddon.hasResource("testfile1")); + Assert.ok(!aAddon.hasResource("testfile2")); + + Assert.equal(aAddon.pendingOperations, AddonManager.PENDING_UPGRADE); + + Assert.equal(aAddon.pendingUpgrade.version, "2.0"); +} + +function check_addon_uninstalling(aAddon, aAfterRestart) { + Assert.notEqual(aAddon, null); + Assert.equal(aAddon.version, "1.0"); + + if (aAfterRestart) { + Assert.ok(!aAddon.isActive); + Assert.ok(!isExtensionInAddonsList(profileDir, aAddon.id)); + } else { + Assert.ok(aAddon.isActive); + Assert.ok(isExtensionInAddonsList(profileDir, aAddon.id)); + } + + Assert.ok(aAddon.hasResource("testfile")); + Assert.ok(aAddon.hasResource("testfile1")); + Assert.ok(!aAddon.hasResource("testfile2")); + + Assert.equal(aAddon.pendingOperations, AddonManager.PENDING_UNINSTALL); +} + +add_task(async function test_1() { + await AddonTestUtils.promiseInstallXPI(ADDONS[0]); + + await promiseRestartManager(); + + let a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + check_addon(a1, "1.0"); + + // Lock either install.rdf for unpacked add-ons or the xpi for packed add-ons. + let uri = a1.getResourceURI("install.rdf"); + if (uri instanceof Ci.nsIJARURI) uri = uri.JARFile; + + let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(uri.QueryInterface(Ci.nsIFileURL).file, -1, 0, 0); + + await AddonTestUtils.promiseInstallXPI(ADDONS[1]); + + check_addon_upgrading(a1); + + await promiseRestartManager(); + + let a1_2 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + check_addon_upgrading(a1_2); + + await promiseRestartManager(); + + let a1_3 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + check_addon_upgrading(a1_3); + + fstream.close(); + + await promiseRestartManager(); + + let a1_4 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + check_addon(a1_4, "2.0"); + + await a1_4.uninstall(); +}); + +// Test that a failed uninstall gets rolled back +add_task(async function test_2() { + await promiseRestartManager(); + + await AddonTestUtils.promiseInstallXPI(ADDONS[0]); + await promiseRestartManager(); + + let a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + check_addon(a1, "1.0"); + + // Lock either install.rdf for unpacked add-ons or the xpi for packed add-ons. + let uri = a1.getResourceURI("install.rdf"); + if (uri instanceof Ci.nsIJARURI) uri = uri.JARFile; + + let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(uri.QueryInterface(Ci.nsIFileURL).file, -1, 0, 0); + + await a1.uninstall(); + + check_addon_uninstalling(a1); + + await promiseRestartManager(); + + let a1_2 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + check_addon_uninstalling(a1_2, true); + + await promiseRestartManager(); + + let a1_3 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + check_addon_uninstalling(a1_3, true); + + fstream.close(); + + await promiseRestartManager(); + + let a1_4 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + Assert.equal(a1_4, null); + var dir = profileDir.clone(); + dir.append(do_get_expected_addon_name("addon1@tests.mozilla.org")); + Assert.ok(!dir.exists()); + Assert.ok(!isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org")); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_builtin_location.js b/toolkit/mozapps/extensions/test/xpcshell/test_builtin_location.js new file mode 100644 index 0000000000..30801e9f72 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_builtin_location.js @@ -0,0 +1,149 @@ +"use strict"; + +/* globals browser */ +let scopes = AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION; +Services.prefs.setIntPref("extensions.enabledScopes", scopes); + +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1" +); + +async function getWrapper(id, hidden) { + let wrapper = await installBuiltinExtension({ + manifest: { + browser_specific_settings: { gecko: { id } }, + hidden, + }, + background() { + browser.test.sendMessage("started"); + }, + }); + await wrapper.awaitMessage("started"); + return wrapper; +} + +// Tests installing an extension from the built-in location. +add_task(async function test_builtin_location() { + let id = "builtin@tests.mozilla.org"; + await AddonTestUtils.promiseStartupManager(); + let wrapper = await getWrapper(id); + + let addon = await promiseAddonByID(id); + notEqual(addon, null, "Addon is installed"); + ok(addon.isActive, "Addon is active"); + ok(addon.isPrivileged, "Addon is privileged"); + ok(wrapper.extension.isAppProvided, "Addon is app provided"); + ok(!addon.hidden, "Addon is not hidden"); + + // Built-in extensions are not checked against the blocklist, + // so we shouldn't have loaded it. + ok(!Services.blocklist.isLoaded, "Blocklist hasn't been loaded"); + + // After a restart, the extension should start up normally. + await promiseRestartManager(); + await wrapper.awaitStartup(); + await wrapper.awaitMessage("started"); + ok(true, "Extension in built-in location ran after restart"); + + addon = await promiseAddonByID(id); + notEqual(addon, null, "Addon is installed"); + ok(addon.isActive, "Addon is active"); + + // After a restart that causes a database rebuild, it should still work + await promiseRestartManager("2"); + await wrapper.awaitStartup(); + await wrapper.awaitMessage("started"); + ok(true, "Extension in built-in location ran after restart"); + + addon = await promiseAddonByID(id); + notEqual(addon, null, "Addon is installed"); + ok(addon.isActive, "Addon is active"); + + // After a restart that changes the schema version, it should still work + await promiseShutdownManager(); + Services.prefs.setIntPref("extensions.databaseSchema", 0); + await promiseStartupManager(); + + await wrapper.awaitStartup(); + await wrapper.awaitMessage("started"); + ok(true, "Extension in built-in location ran after restart"); + + addon = await promiseAddonByID(id); + notEqual(addon, null, "Addon is installed"); + ok(addon.isActive, "Addon is active"); + + await wrapper.unload(); + + addon = await promiseAddonByID(id); + equal(addon, null, "Addon is gone after uninstall"); + await AddonTestUtils.promiseShutdownManager(); +}); + +// Tests installing a hidden extension from the built-in location. +add_task(async function test_builtin_location_hidden() { + let id = "hidden@tests.mozilla.org"; + await AddonTestUtils.promiseStartupManager(); + let wrapper = await getWrapper(id, true); + + let addon = await promiseAddonByID(id); + notEqual(addon, null, "Addon is installed"); + ok(addon.isActive, "Addon is active"); + ok(addon.isPrivileged, "Addon is privileged"); + ok(wrapper.extension.isAppProvided, "Addon is app provided"); + ok(addon.hidden, "Addon is hidden"); + + await wrapper.unload(); + await AddonTestUtils.promiseShutdownManager(); +}); + +// Tests updates to builtin extensions +add_task(async function test_builtin_update() { + let id = "bleah@tests.mozilla.org"; + await AddonTestUtils.promiseStartupManager(); + + let wrapper = await installBuiltinExtension({ + manifest: { + browser_specific_settings: { gecko: { id } }, + version: "1.0", + }, + background() { + browser.test.sendMessage("started"); + }, + }); + await wrapper.awaitMessage("started"); + + await AddonTestUtils.promiseShutdownManager(); + + // Change the built-in + await setupBuiltinExtension({ + manifest: { + browser_specific_settings: { gecko: { id } }, + version: "2.0", + }, + background() { + browser.test.sendMessage("started"); + }, + }); + + let updateReason; + AddonTestUtils.on("bootstrap-method", (method, params, reason) => { + updateReason = reason; + }); + + // Re-start, with a new app version + await AddonTestUtils.promiseStartupManager("3"); + + await wrapper.awaitMessage("started"); + + equal( + updateReason, + BOOTSTRAP_REASONS.ADODN_UPGRADE, + "Builtin addon's bootstrap update() method was called at startup" + ); + + await wrapper.unload(); + await AddonTestUtils.promiseShutdownManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js new file mode 100644 index 0000000000..f6039b29bf --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that flushing the zipreader cache happens when appropriate + +var gExpectedFile = null; +var gCacheFlushCount = 0; + +var CacheFlushObserver = { + observe(aSubject, aTopic, aData) { + if (aTopic != "flush-cache-entry") { + return; + } + + // Ignore flushes from the fake cert DB or extension-process-script + if (aData == "cert-override" || aSubject == null) { + return; + } + + if (!gExpectedFile) { + return; + } + ok(aSubject instanceof Ci.nsIFile); + equal(aSubject.path, gExpectedFile.path); + gCacheFlushCount++; + }, +}; + +add_task(async function setup() { + Services.obs.addObserver(CacheFlushObserver, "flush-cache-entry"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "2"); + + await promiseStartupManager(); +}); + +// Tests that the cache is flushed when installing a restartless add-on +add_task(async function test_flush_restartless_install() { + let xpi = await createTempWebExtensionFile({ + manifest: { + name: "Cache Flush Test", + version: "2.0", + browser_specific_settings: { gecko: { id: "addon2@tests.mozilla.org" } }, + }, + }); + + let install = await AddonManager.getInstallForFile(xpi); + + await new Promise(resolve => { + install.addListener({ + onInstallStarted() { + // We should flush the staged XPI when completing the install + gExpectedFile = gProfD.clone(); + gExpectedFile.append("extensions"); + gExpectedFile.append("staged"); + gExpectedFile.append("addon2@tests.mozilla.org.xpi"); + }, + + onInstallEnded() { + equal(gCacheFlushCount, 1); + gExpectedFile = null; + gCacheFlushCount = 0; + + resolve(); + }, + }); + + install.install(); + }); +}); + +// Tests that the cache is flushed when uninstalling a restartless add-on +add_task(async function test_flush_uninstall() { + let addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + + // We should flush the installed XPI when uninstalling + gExpectedFile = gProfD.clone(); + gExpectedFile.append("extensions"); + gExpectedFile.append("addon2@tests.mozilla.org.xpi"); + + await addon.uninstall(); + + ok(gCacheFlushCount >= 1); + gExpectedFile = null; + gCacheFlushCount = 0; +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_childprocess.js b/toolkit/mozapps/extensions/test/xpcshell/test_childprocess.js new file mode 100644 index 0000000000..d7661d52ad --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_childprocess.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that the AddonManager refuses to load in child processes. + +// NOTE: This test does NOT load head_addons.js, because that would indirectly +// load AddonManager.sys.mjs. In this test, we want to be the first to load the +// AddonManager module to verify that it cannot be loaded in child processes. + +const { updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" +); + +function run_test() { + updateAppInfo(); + Services.appinfo.processType = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; + try { + ChromeUtils.importESModule("resource://gre/modules/AddonManager.sys.mjs"); + do_throw("AddonManager should have refused to load"); + } catch (ex) { + info(ex.message); + Assert.ok(!!ex.message); + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_colorways_builtin_theme_upgrades.js b/toolkit/mozapps/extensions/test/xpcshell/test_colorways_builtin_theme_upgrades.js new file mode 100644 index 0000000000..6449481f67 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_colorways_builtin_theme_upgrades.js @@ -0,0 +1,582 @@ +"use strict"; + +const { BuiltInThemes } = ChromeUtils.importESModule( + "resource:///modules/BuiltInThemes.sys.mjs" +); +const { BuiltInThemeConfig } = ChromeUtils.importESModule( + "resource:///modules/BuiltInThemeConfig.sys.mjs" +); + +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); + +// Enable SCOPE_APPLICATION for builtin testing. +let scopes = AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION; +Services.prefs.setIntPref("extensions.enabledScopes", scopes); + +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "42", + "42" +); + +const ADDON_ID = "mock-colorway@mozilla.org"; +const ADDON_ID_RETAINED = "mock-disabled-retained-colorway@mozilla.org"; +const ADDON_ID_NOT_RETAINED = "mock-disabled-not-retained-colorway@mozilla.org"; +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; +const NOT_MIGRATED_THEME = "mock-not-migrated-theme@mozilla.org"; + +const RETAINED_THEMES_PREF = "browser.theme.retainedExpiredThemes"; +const COLORWAY_MIGRATION_PREF = "browser.theme.colorway-migration"; + +const ICON_SVG = ` + + + + + + + + + +`; +const createMockThemeManifest = (id, version) => ({ + name: "A mock colorway theme", + author: "Mozilla", + version, + icons: { 32: "icon.svg" }, + theme: { + colors: { + toolbar: "red", + }, + }, + browser_specific_settings: { + gecko: { id }, + }, +}); + +let server = createHttpServer(); + +const SERVER_BASE_URL = `http://localhost:${server.identity.primaryPort}`; + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); +Services.prefs.setCharPref( + "extensions.update.background.url", + `${SERVER_BASE_URL}/upgrade.json` +); + +AddonTestUtils.registerJSON(server, "/upgrade.json", { + addons: { + [ADDON_ID]: { + updates: [ + { + version: "2.0.0", + update_link: `${SERVER_BASE_URL}/${ADDON_ID}.xpi`, + }, + ], + }, + [ADDON_ID_RETAINED]: { + updates: [ + { + version: "3.0.0", + update_link: `${SERVER_BASE_URL}/${ADDON_ID_RETAINED}.xpi`, + }, + ], + }, + // We list the test extension with addon id ADDON_ID_NOT_RETAINED here, + // but the xpi file doesn't exist because we expect that we wouldn't + // be checking this extension for updates, and that expected behavior + // regresses, the test would fail either for the explicit assertion + // (checking that we don't find an update) or because we would be trying + // to download a file from an url that isn't going to be handled. + [ADDON_ID_NOT_RETAINED]: { + updates: [ + { + version: "4.0.0", + update_link: `${SERVER_BASE_URL}/non-existing.xpi`, + }, + ], + }, + }, +}); + +function createWebExtensionFile(id, version) { + return AddonTestUtils.createTempWebExtensionFile({ + files: { "icon.svg": ICON_SVG }, + manifest: createMockThemeManifest(id, version), + }); +} + +let xpiUpdate = createWebExtensionFile(ADDON_ID, "2.0.0"); +let retainedThemeUpdate = createWebExtensionFile(ADDON_ID_RETAINED, "3.0.0"); + +server.registerFile(`/${ADDON_ID}.xpi`, xpiUpdate); +server.registerFile(`/${ADDON_ID_RETAINED}.xpi`, retainedThemeUpdate); + +function assertAddonWrapperProperties( + addonWrapper, + { id, version, isBuiltin, type, isBuiltinColorwayTheme, scope } +) { + Assert.deepEqual( + { + id: addonWrapper.id, + version: addonWrapper.version, + type: addonWrapper.type, + scope: addonWrapper.scope, + isBuiltin: addonWrapper.isBuiltin, + isBuiltinColorwayTheme: addonWrapper.isBuiltinColorwayTheme, + }, + { + id, + version, + type, + scope, + isBuiltin, + isBuiltinColorwayTheme, + }, + `Got expected properties on addon wrapper for "${id}"` + ); +} + +function assertAddonCanUpgrade(addonWrapper, canUpgrade) { + equal( + !!(addonWrapper.permissions & AddonManager.PERM_CAN_UPGRADE), + canUpgrade, + `Expected "${addonWrapper.id}" to ${ + canUpgrade ? "have" : "not have" + } PERM_CAN_UPGRADE AOM permission` + ); +} + +function assertIsActiveThemeID(addonId) { + equal( + Services.prefs.getCharPref("extensions.activeThemeID"), + addonId, + `Expect ${addonId} to be the currently active theme` + ); +} + +function assertIsExpiredTheme(addonId, expectExpired) { + equal( + // themeIsExpired returns undefined for themes without an expiry date, + // normalized here to always be a boolean. + !!BuiltInThemes.themeIsExpired(addonId), + expectExpired, + `Expect ${addonId} to be recognized as an expired colorway theme` + ); +} + +function assertIsRetainedExpiredTheme(addonId, expectRetainedExpired) { + equal( + BuiltInThemes.isRetainedExpiredTheme(addonId), + expectRetainedExpired, + `Expect ${addonId} to be recognized as a retained expired colorway theme` + ); +} + +function waitForBootstrapUpdateMethod(addonId, newVersion) { + return new Promise(resolve => { + function listener(_evt, { method, params }) { + if ( + method === "update" && + params.id === addonId && + params.newVersion === newVersion + ) { + AddonTestUtils.off("bootstrap-method", listener); + info(`Update bootstrap method called for ${addonId} ${newVersion}`); + resolve(); + } + } + AddonTestUtils.on("bootstrap-method", listener); + }); +} + +let waitForTemporaryXPIFilesRemoved; + +add_setup(async () => { + info("Creating BuiltInThemes stubs"); + const sandbox = sinon.createSandbox(); + // Restoring the mocked BuiltInThemeConfig doesn't really matter for xpcshell + // because each test file will run in its own separate xpcshell instance, + // but cleaning it up doesn't harm neither. + registerCleanupFunction(() => { + info("Restoring BuiltInThemes sandbox for cleanup"); + sandbox.restore(); + BuiltInThemes.builtInThemeMap = BuiltInThemeConfig; + }); + + // Mock BuiltInThemes builtInThemeMap. + BuiltInThemes.builtInThemeMap = new Map(); + sandbox.stub(BuiltInThemes.builtInThemeMap, "get").callsFake(id => { + info(`Mock BuiltInthemes.builtInThemeMap.get result for ${id}`); + // No theme info is expected to be returned for the default-theme. + if (id === DEFAULT_THEME_ID) { + return undefined; + } + if (!id.endsWith("colorway@mozilla.org")) { + return BuiltInThemeConfig.get(id); + } + let mockThemeProperties = { + collection: "Mock expired colorway collection", + figureUrl: "about:blank", + expiry: new Date("1970-01-01"), + }; + return mockThemeProperties; + }); + + // Start AOM and make sure updates are enabled. + await AddonTestUtils.promiseStartupManager(); + AddonManager.updateEnabled = true; + + // Enable the default theme explicitly (mainly because on DevEdition builds + // the dark theme would be the one enabled by default). + const defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME_ID); + await defaultTheme.enable(); + assertIsActiveThemeID(defaultTheme.id); + + const tmpFiles = new Set(); + const addonInstallListener = { + onInstallEnded: function collectTmpFiles(install) { + tmpFiles.add(install.file); + }, + }; + AddonManager.addInstallListener(addonInstallListener); + registerCleanupFunction(() => { + AddonManager.removeInstallListener(addonInstallListener); + }); + + // Make sure all the tempfile created for the background updates have + // been removed (otherwise AddonTestUtils cleanup function will trigger + // intermittent test failures due to unexpected xpi files that may still + // be found in the temporary directory). + waitForTemporaryXPIFilesRemoved = async () => { + info( + "Wait for temporary xpi files created by the background updates to have been removed" + ); + const files = Array.from(tmpFiles); + tmpFiles.clear(); + await TestUtils.waitForCondition(async () => { + for (const file of files) { + if (await file.exists()) { + return false; + } + } + return true; + }, "Wait for the temporary files created for the background updates to have been removed"); + }; +}); + +add_task( + { + pref_set: [[COLORWAY_MIGRATION_PREF, false]], + }, + async function test_colorways_migration_disabled() { + info("Install and activate a colorway built-in test theme"); + + await installBuiltinExtension( + { + manifest: createMockThemeManifest(ADDON_ID, "1.0.0"), + }, + false /* waitForStartup */ + ); + const activeTheme = await AddonManager.getAddonByID(ADDON_ID); + assertAddonWrapperProperties(activeTheme, { + id: ADDON_ID, + version: "1.0.0", + type: "theme", + scope: AddonManager.SCOPE_APPLICATION, + isBuiltin: true, + isBuiltinColorwayTheme: true, + }); + const promiseThemeEnabled = AddonTestUtils.promiseAddonEvent( + "onEnabled", + addon => addon.id === ADDON_ID + ); + await activeTheme.enable(); + await promiseThemeEnabled; + ok(activeTheme.isActive, "Expect the colorways theme to be active"); + assertIsActiveThemeID(activeTheme.id); + + info("Verify that built-in colorway migration is disabled as expected"); + + assertAddonCanUpgrade(activeTheme, false); + + const promiseBackgroundUpdatesFound = TestUtils.topicObserved( + "addons-background-updates-found" + ); + await AddonManagerPrivate.backgroundUpdateCheck(); + const [, numUpdatesFound] = await promiseBackgroundUpdatesFound; + equal(numUpdatesFound, 0, "Expect no add-on updates to be found"); + + await activeTheme.uninstall(); + } +); + +add_task( + { + pref_set: [ + [COLORWAY_MIGRATION_PREF, true], + [ + RETAINED_THEMES_PREF, + JSON.stringify([ADDON_ID_RETAINED, NOT_MIGRATED_THEME]), + ], + ], + }, + async function test_colorways_builtin_upgrade() { + info("Verify default theme initially enabled"); + const defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME_ID); + assertAddonWrapperProperties(defaultTheme, { + id: DEFAULT_THEME_ID, + version: defaultTheme.version, + type: "theme", + scope: AddonManager.SCOPE_APPLICATION, + isBuiltin: true, + isBuiltinColorwayTheme: false, + }); + ok( + defaultTheme.isActive, + "Expect the default theme to be initially active" + ); + assertIsActiveThemeID(defaultTheme.id); + + info("Install the non retained expired colorway test theme"); + await installBuiltinExtension( + { + manifest: createMockThemeManifest(ADDON_ID_NOT_RETAINED, "1.0.0"), + }, + false /* waitForStartup */ + ); + const notRetainedTheme = await AddonManager.getAddonByID( + ADDON_ID_NOT_RETAINED + ); + assertAddonWrapperProperties(notRetainedTheme, { + id: ADDON_ID_NOT_RETAINED, + version: "1.0.0", + type: "theme", + scope: AddonManager.SCOPE_APPLICATION, + isBuiltin: true, + isBuiltinColorwayTheme: true, + }); + + info("Install the retained expired colorway test theme"); + await installBuiltinExtension( + { + manifest: createMockThemeManifest(ADDON_ID_RETAINED, "1.0.0"), + }, + false /* waitForStartup */ + ); + const retainedTheme = await AddonManager.getAddonByID(ADDON_ID_RETAINED); + assertAddonWrapperProperties(retainedTheme, { + id: ADDON_ID_RETAINED, + version: "1.0.0", + type: "theme", + scope: AddonManager.SCOPE_APPLICATION, + isBuiltin: true, + isBuiltinColorwayTheme: true, + }); + + info("Install the active colorway test theme"); + await installBuiltinExtension( + { + manifest: createMockThemeManifest(ADDON_ID, "1.0.0"), + }, + false /* waitForStartup */ + ); + const activeTheme = await AddonManager.getAddonByID(ADDON_ID); + assertAddonWrapperProperties(activeTheme, { + id: ADDON_ID, + version: "1.0.0", + type: "theme", + scope: AddonManager.SCOPE_APPLICATION, + isBuiltin: true, + isBuiltinColorwayTheme: true, + }); + const promiseThemeEnabled = AddonTestUtils.promiseAddonEvent( + "onEnabled", + addon => addon.id === ADDON_ID + ); + await activeTheme.enable(); + await promiseThemeEnabled; + ok(activeTheme.isActive, "Expect the colorways theme to be active"); + assertIsActiveThemeID(activeTheme.id); + + info("Verify only active and retained colorways themes can be upgraded"); + assertIsActiveThemeID(activeTheme.id); + assertIsExpiredTheme(activeTheme.id, true); + assertIsRetainedExpiredTheme(activeTheme.id, false); + + assertIsExpiredTheme(retainedTheme.id, true); + assertIsRetainedExpiredTheme(retainedTheme.id, true); + + assertIsExpiredTheme(notRetainedTheme.id, true); + assertIsRetainedExpiredTheme(notRetainedTheme.id, false); + + assertIsExpiredTheme(defaultTheme.id, false); + assertIsRetainedExpiredTheme(defaultTheme.id, false); + + assertAddonCanUpgrade(retainedTheme, true); + assertAddonCanUpgrade(notRetainedTheme, false); + assertAddonCanUpgrade(activeTheme, true); + // Make sure a non-colorways built-in theme cannot check for updates. + assertAddonCanUpgrade(defaultTheme, false); + + Assert.deepEqual( + Services.prefs.getStringPref(RETAINED_THEMES_PREF), + JSON.stringify([retainedTheme.id, NOT_MIGRATED_THEME]), + `Expect the retained theme id to be listed in the ${RETAINED_THEMES_PREF} pref` + ); + + const promiseUpdatesInstalled = Promise.all([ + waitForBootstrapUpdateMethod(ADDON_ID, "2.0.0"), + waitForBootstrapUpdateMethod(ADDON_ID_RETAINED, "3.0.0"), + ]); + + const promiseInstallsEnded = Promise.all([ + AddonTestUtils.promiseInstallEvent( + "onInstallEnded", + addon => addon.id === ADDON_ID + ), + AddonTestUtils.promiseInstallEvent( + "onInstallEnded", + addon => addon.id === ADDON_ID_RETAINED + ), + ]); + + const promiseActiveThemeStartupCompleted = + AddonTestUtils.promiseWebExtensionStartup(ADDON_ID); + + const promiseBackgroundUpdatesFound = TestUtils.topicObserved( + "addons-background-updates-found" + ); + await AddonManagerPrivate.backgroundUpdateCheck(); + const [, numUpdatesFound] = await promiseBackgroundUpdatesFound; + equal(numUpdatesFound, 2, "Expect 2 add-on updates to have been found"); + + info("Wait for the 2 expected updates to be completed"); + await promiseUpdatesInstalled; + + const updatedActiveTheme = await AddonManager.getAddonByID(ADDON_ID); + assertAddonWrapperProperties(updatedActiveTheme, { + id: ADDON_ID, + version: "2.0.0", + type: "theme", + scope: AddonManager.SCOPE_PROFILE, + isBuiltin: false, + isBuiltinColorwayTheme: false, + }); + // Expect the updated active theme to stay set as the currently active theme. + assertIsActiveThemeID(updatedActiveTheme.id); + + info("Verify addon update on disabled builtin colorway theme"); + + const updatedRetainedTheme = await AddonManager.getAddonByID( + ADDON_ID_RETAINED + ); + assertAddonWrapperProperties(updatedRetainedTheme, { + id: ADDON_ID_RETAINED, + version: "3.0.0", + type: "theme", + scope: AddonManager.SCOPE_PROFILE, + isBuiltin: false, + isBuiltinColorwayTheme: false, + }); + // Expect the updated active theme to stay set as the currently active theme. + assertIsActiveThemeID(updatedActiveTheme.id); + ok(updatedActiveTheme.isActive, "Expect the colorways theme to be active"); + + // We need to wait for the active theme startup otherwise uninstall the active + // theme will fail to remove the xpi file because it is stil active while the + // test is running on windows builds. + info("Wait for the active theme to have been fully loaded"); + await promiseActiveThemeStartupCompleted; + + await promiseInstallsEnded; + + Assert.deepEqual( + Services.prefs.getStringPref(RETAINED_THEMES_PREF), + JSON.stringify([NOT_MIGRATED_THEME]), + `Expect migrated retained theme to not be listed anymore in the ${RETAINED_THEMES_PREF} pref` + ); + + info( + "uninstall test colorways themes and expect default theme to become active" + ); + + const promiseUninstalled = Promise.all([ + AddonTestUtils.promiseAddonEvent( + "onUninstalled", + addon => addon.id === ADDON_ID + ), + AddonTestUtils.promiseAddonEvent( + "onUninstalled", + addon => addon.id === ADDON_ID_RETAINED + ), + AddonTestUtils.promiseAddonEvent( + "onUninstalled", + addon => addon.id === ADDON_ID_NOT_RETAINED + ), + ]); + + const promiseDefaultThemeEnabled = + AddonTestUtils.promiseAddonEvent("onEnabled"); + + await updatedActiveTheme.uninstall(); + await updatedRetainedTheme.uninstall(); + await notRetainedTheme.uninstall(); + + await promiseUninstalled; + + info("Wait for the default theme to become active"); + // Waiting explicitly for the onEnabled addon event prevents a race between + // the test task exiting (and the AddonManager being shutdown automatically + // as a side effect of that) and the XPIProvider trying to call the addon event + // listeners for the default theme being enabled), which would trigger a test + // failure after the test is existing. + await promiseDefaultThemeEnabled; + + ok(defaultTheme.isActive, "Expect the default theme to be active"); + assertIsActiveThemeID(defaultTheme.id); + + // Wait for the temporary file to be actually removed, otherwise the hack we use + // to mock an AOM restart (which is unloading the related jsm modules) may + // affect the successfull removal of the temporary file because some of the + // global helpers defined and used inside the XPIProvider may have been gone + // already and intermittently trigger unexpected errors. + await waitForTemporaryXPIFilesRemoved(); + + // Restart the addon manager to confirm that the migrated colorways themes + // are still gone after an AOM restart and that the previously installed + // builtin hasn't been made implicitly visible again. + info( + "Verify old builtin colorways are not visible and default-theme still active after AOM restart" + ); + await AddonTestUtils.promiseRestartManager(); + + const defaultThemeAfterRestart = await AddonManager.getAddonByID( + DEFAULT_THEME_ID + ); + ok( + defaultThemeAfterRestart.isActive, + "Expect the default theme to be active" + ); + + equal( + (await AddonManager.getAddonByID(ADDON_ID))?.version, + undefined, + "Expect the active theme addon to not be available anymore after being uninstalled" + ); + equal( + (await AddonManager.getAddonByID(ADDON_ID_RETAINED))?.version, + undefined, + "Expect the retained theme addon to not be available anymore after being uninstalled" + ); + equal( + (await AddonManager.getAddonByID(ADDON_ID_NOT_RETAINED))?.version, + undefined, + "Expect the not retained expired theme addon to not be available anymore after being uninstalled" + ); + } +); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_cookies.js b/toolkit/mozapps/extensions/test/xpcshell/test_cookies.js new file mode 100644 index 0000000000..56f745929b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_cookies.js @@ -0,0 +1,102 @@ +"use strict"; + +let server = createHttpServer({ hosts: ["example.com"] }); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "45", "45"); + +Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true); +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +// Tests that cookies are not sent with background requests. +add_task(async function test_cookies() { + const ID = "bg-cookies@tests.mozilla.org"; + + // Add a new handler to the test web server for the given file path. + // The handler appends the incoming requests to `results` and replies + // with the provided body. + function makeHandler(path, results, body) { + server.registerPathHandler(path, (request, response) => { + results.push(request); + response.write(body); + }); + } + + let gets = []; + makeHandler("/get", gets, JSON.stringify({ results: [] })); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, "http://example.com/get"); + + let updates = []; + makeHandler( + "/update", + updates, + JSON.stringify({ + addons: { + [ID]: { + updates: [ + { + version: "2.0", + update_link: "http://example.com/update.xpi", + applications: { + gecko: {}, + }, + }, + ], + }, + }, + }) + ); + + let xpiFetches = []; + makeHandler("/update.xpi", xpiFetches, ""); + + const COOKIE = "test"; + // cookies.add() takes a time in seconds + let expiration = Date.now() / 1000 + 60 * 60; + Services.cookies.add( + "example.com", + "/", + COOKIE, + "testing", + false, + false, + false, + expiration, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTP + ); + + await promiseStartupManager(); + + let addon = await promiseInstallWebExtension({ + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: ID, + update_url: "http://example.com/update", + }, + }, + }, + }); + + equal(gets.length, 1, "Saw one addon metadata request"); + equal(gets[0].hasHeader("Cookie"), false, "Metadata request has no cookies"); + + await Promise.all([ + AddonTestUtils.promiseInstallEvent("onDownloadFailed"), + AddonManagerPrivate.backgroundUpdateCheck(), + ]); + + equal(updates.length, 1, "Saw one update check request"); + equal(updates[0].hasHeader("Cookie"), false, "Update request has no cookies"); + + equal(xpiFetches.length, 1, "Saw one request for updated xpi"); + equal( + xpiFetches[0].hasHeader("Cookie"), + false, + "Request for updated XPI has no cookies" + ); + + await addon.uninstall(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js new file mode 100644 index 0000000000..727c643763 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -0,0 +1,216 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Checks that we rebuild something sensible from a corrupt database + +// Create and configure the HTTP server. +var testserver = createHttpServer({ hosts: ["example.com"] }); + +// register files with server +testserver.registerDirectory("/data/", do_get_file("data")); + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +const ADDONS = { + // Will get a compatibility update and stay enabled + "addon3@tests.mozilla.org": { + manifest: { + name: "Test 3", + browser_specific_settings: { + gecko: { + id: "addon3@tests.mozilla.org", + update_url: "http://example.com/data/test_corrupt.json", + }, + }, + }, + findUpdates: true, + desiredState: { + isActive: true, + userDisabled: false, + appDisabled: false, + pendingOperations: 0, + }, + }, + + // Will get a compatibility update and be enabled + "addon4@tests.mozilla.org": { + manifest: { + name: "Test 4", + browser_specific_settings: { + gecko: { + id: "addon4@tests.mozilla.org", + update_url: "http://example.com/data/test_corrupt.json", + }, + }, + }, + initialState: { + userDisabled: true, + }, + findUpdates: true, + desiredState: { + isActive: false, + userDisabled: true, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "addon5@tests.mozilla.org": { + manifest: { + name: "Test 5", + browser_specific_settings: { gecko: { id: "addon5@tests.mozilla.org" } }, + }, + desiredState: { + isActive: true, + userDisabled: false, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "addon7@tests.mozilla.org": { + manifest: { + name: "Test 7", + browser_specific_settings: { gecko: { id: "addon7@tests.mozilla.org" } }, + }, + initialState: { + userDisabled: true, + }, + desiredState: { + isActive: false, + userDisabled: true, + appDisabled: false, + pendingOperations: 0, + }, + }, + + // The default theme + "theme1@tests.mozilla.org": { + manifest: { + manifest_version: 2, + name: "Theme 1", + version: "1.0", + theme: { images: { theme_frame: "example.png" } }, + browser_specific_settings: { + gecko: { + id: "theme1@tests.mozilla.org", + }, + }, + }, + desiredState: { + isActive: false, + userDisabled: true, + appDisabled: false, + pendingOperations: 0, + }, + }, + + "theme2@tests.mozilla.org": { + manifest: { + manifest_version: 2, + name: "Theme 2", + version: "1.0", + theme: { images: { theme_frame: "example.png" } }, + browser_specific_settings: { + gecko: { + id: "theme2@tests.mozilla.org", + }, + }, + }, + initialState: { + userDisabled: false, + }, + desiredState: { + isActive: true, + userDisabled: false, + appDisabled: false, + pendingOperations: 0, + }, + }, +}; + +const IDS = Object.keys(ADDONS); + +function promiseUpdates(addon) { + return new Promise(resolve => { + addon.findUpdates( + { onUpdateFinished: resolve }, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + }); +} + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2"); + + for (let addon of Object.values(ADDONS)) { + let webext = createTempWebExtensionFile({ manifest: addon.manifest }); + await AddonTestUtils.manuallyInstall(webext); + } + + await promiseStartupManager(); + + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + if (addon.initialState) { + await setInitialState(addons.get(id), addon.initialState); + } + if (addon.findUpdates) { + await promiseUpdates(addons.get(id)); + } + } +}); + +add_task(async function test_after_restart() { + await promiseRestartManager(); + + info("Test add-on state after restart"); + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + checkAddon(id, addons.get(id), addon.desiredState); + } + + await promiseShutdownManager(); +}); + +add_task(async function test_after_corruption() { + // Shutdown and replace the database with a corrupt file (a directory + // serves this purpose). On startup the add-ons manager won't rebuild + // because there is a file there still. + gExtensionsJSON.remove(true); + gExtensionsJSON.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + await promiseStartupManager(); + + Services.obs.notifyObservers(null, "sessionstore-windows-restored"); + await AddonManagerPrivate.databaseReady; + + // Accessing the add-ons should open and recover the database + info("Test add-on state after corruption"); + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + checkAddon(id, addons.get(id), addon.desiredState); + } + + await Assert.rejects( + promiseShutdownManager(), + /NotAllowedError: Could not open the file at .+ for writing/ + ); +}); + +add_task(async function test_after_second_restart() { + await promiseStartupManager(); + + info("Test add-on state after second restart"); + let addons = await getAddons(IDS); + for (let [id, addon] of Object.entries(ADDONS)) { + checkAddon(id, addons.get(id), addon.desiredState); + } + + await Assert.rejects( + promiseShutdownManager(), + /NotAllowedError: Could not open the file at .+ for writing/ + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_crash_annotation_quoting.js b/toolkit/mozapps/extensions/test/xpcshell/test_crash_annotation_quoting.js new file mode 100644 index 0000000000..4458c6d592 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_crash_annotation_quoting.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that strange characters in an add-on version don't break the +// crash annotation. + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + +add_task(async function run_test() { + await promiseStartupManager(); + + let n = 1; + for (let version in ["1,0", "1:0"]) { + let id = `addon${n++}@tests.mozilla.org`; + await promiseInstallWebExtension({ + manifest: { + version, + browser_specific_settings: { gecko: { id } }, + }, + }); + + do_check_in_crash_annotation(id, version); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_db_path.js b/toolkit/mozapps/extensions/test/xpcshell/test_db_path.js new file mode 100644 index 0000000000..a9a54291f0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_db_path.js @@ -0,0 +1,64 @@ +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; + +let global = this; + +// Test that paths in the extensions database are stored properly +// if they include non-ascii characters (see bug 1428234 for an example of +// a past bug with such paths) +add_task(async function test_non_ascii_path() { + const PROFILE_VAR = "XPCSHELL_TEST_PROFILE_DIR"; + let profileDir = PathUtils.join( + Services.env.get(PROFILE_VAR), + "\u00ce \u00e5m \u00f1\u00f8t \u00e5s\u00e7ii" + ); + Services.env.set(PROFILE_VAR, profileDir); + + AddonTestUtils.init(global); + AddonTestUtils.overrideCertDB(); + AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1" + ); + + const ID1 = "profile1@tests.mozilla.org"; + let xpi1 = await AddonTestUtils.createTempWebExtensionFile({ + id: ID1, + manifest: { + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + + const ID2 = "profile2@tests.mozilla.org"; + let xpi2 = await AddonTestUtils.createTempWebExtensionFile({ + id: ID2, + manifest: { + browser_specific_settings: { gecko: { id: ID2 } }, + }, + }); + + await AddonTestUtils.manuallyInstall(xpi1); + await AddonTestUtils.promiseStartupManager(); + await AddonTestUtils.promiseInstallFile(xpi2); + await AddonTestUtils.promiseShutdownManager(); + + let dbfile = PathUtils.join(profileDir, "extensions.json"); + let data = await IOUtils.readJSON(dbfile); + + let addons = data.addons.filter(a => a.id !== DEFAULT_THEME_ID); + Assert.ok(Array.isArray(addons), "extensions.json has addons array"); + Assert.equal(2, addons.length, "extensions.json has 2 addons"); + Assert.ok( + addons[0].path.startsWith(profileDir), + "path property for sideloaded extension has the proper profile directory" + ); + Assert.ok( + addons[1].path.startsWith(profileDir), + "path property for extension installed at runtime has the proper profile directory" + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js b/toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js new file mode 100644 index 0000000000..7b1c6fbef9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js @@ -0,0 +1,556 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that delaying an update works for WebExtensions. + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +if (AppConstants.platform == "win" && AppConstants.DEBUG) { + // Shutdown timing is flaky in this test, and remote extensions + // sometimes wind up leaving the XPI locked at the point when we try + // to remove it. + Services.prefs.setBoolPref("extensions.webextensions.remote", false); +} + +PromiseTestUtils.allowMatchingRejectionsGlobally( + /Message manager disconnected/ +); + +/* globals browser*/ + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); +const stageDir = profileDir.clone(); +stageDir.append("staged"); + +const IGNORE_ID = "test_delay_update_ignore_webext@tests.mozilla.org"; +const COMPLETE_ID = "test_delay_update_complete_webext@tests.mozilla.org"; +const DEFER_ID = "test_delay_update_defer_webext@tests.mozilla.org"; +const STAGED_ID = "test_delay_update_staged_webext@tests.mozilla.org"; +const STAGED_NO_UPDATE_URL_ID = + "test_delay_update_staged_webext_no_update_url@tests.mozilla.org"; +const NOUPDATE_ID = "test_no_update_webext@tests.mozilla.org"; + +// Create and configure the HTTP server. +var testserver = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); +testserver.registerDirectory("/data/", do_get_file("data")); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42"); +BootstrapMonitor.init(); + +const ADDONS = { + test_delay_update_complete_webextension_v2: { + "manifest.json": { + manifest_version: 2, + name: "Delay Upgrade", + version: "2.0", + browser_specific_settings: { + gecko: { id: COMPLETE_ID }, + }, + }, + }, + test_delay_update_defer_webextension_v2: { + "manifest.json": { + manifest_version: 2, + name: "Delay Upgrade", + version: "2.0", + browser_specific_settings: { + gecko: { id: DEFER_ID }, + }, + }, + }, + test_delay_update_staged_webextension_v2: { + "manifest.json": { + manifest_version: 2, + name: "Delay Upgrade", + version: "2.0", + browser_specific_settings: { + gecko: { + id: STAGED_ID, + update_url: `http://example.com/data/test_delay_updates_staged.json`, + strict_min_version: "1", + strict_max_version: "41", + }, + }, + }, + }, + test_delay_update_staged_webextension_no_update_url_v2: { + "manifest.json": { + manifest_version: 2, + name: "Delay Upgrade", + version: "2.0", + browser_specific_settings: { + gecko: { + id: STAGED_NO_UPDATE_URL_ID, + strict_min_version: "1", + strict_max_version: "41", + }, + }, + }, + }, + test_delay_update_ignore_webextension_v2: { + "manifest.json": { + manifest_version: 2, + name: "Delay Upgrade", + version: "2.0", + browser_specific_settings: { + gecko: { id: IGNORE_ID }, + }, + }, + }, +}; + +const XPIS = {}; +for (let [name, files] of Object.entries(ADDONS)) { + XPIS[name] = AddonTestUtils.createTempXPIFile(files); + testserver.registerFile(`/addons/${name}.xpi`, XPIS[name]); +} + +// add-on registers upgrade listener, and ignores update. +add_task(async function delay_updates_ignore() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: IGNORE_ID, + update_url: `http://example.com/data/test_delay_updates_ignore.json`, + }, + }, + }, + background() { + browser.runtime.onUpdateAvailable.addListener(details => { + if (details) { + if (details.version) { + // This should be the version of the pending update. + browser.test.assertEq("2.0", details.version, "correct version"); + browser.test.notifyPass("delay"); + } + } else { + browser.test.fail("no details object passed"); + } + }); + browser.test.sendMessage("ready"); + }, + }); + + await Promise.all([extension.startup(), extension.awaitMessage("ready")]); + BootstrapMonitor.checkInstalled(IGNORE_ID, "1.0"); + + let addon = await promiseAddonByID(IGNORE_ID); + Assert.notEqual(addon, null); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.name, "Generated extension"); + Assert.ok(addon.isCompatible); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + Assert.equal(addon.type, "extension"); + + let update = await promiseFindAddonUpdates(addon); + let install = update.updateAvailable; + + await promiseCompleteAllInstalls([install]); + + Assert.equal(install.state, AddonManager.STATE_POSTPONED); + BootstrapMonitor.checkInstalled(IGNORE_ID, "1.0"); + + // addon upgrade has been delayed + let addon_postponed = await promiseAddonByID(IGNORE_ID); + Assert.notEqual(addon_postponed, null); + Assert.equal(addon_postponed.version, "1.0"); + Assert.equal(addon_postponed.name, "Generated extension"); + Assert.ok(addon_postponed.isCompatible); + Assert.ok(!addon_postponed.appDisabled); + Assert.ok(addon_postponed.isActive); + Assert.equal(addon_postponed.type, "extension"); + + await extension.awaitFinish("delay"); + + // restarting allows upgrade to proceed + await promiseRestartManager(); + + let addon_upgraded = await promiseAddonByID(IGNORE_ID); + await extension.awaitStartup(); + BootstrapMonitor.checkUpdated(IGNORE_ID, "2.0"); + + Assert.notEqual(addon_upgraded, null); + Assert.equal(addon_upgraded.version, "2.0"); + Assert.equal(addon_upgraded.name, "Delay Upgrade"); + Assert.ok(addon_upgraded.isCompatible); + Assert.ok(!addon_upgraded.appDisabled); + Assert.ok(addon_upgraded.isActive); + Assert.equal(addon_upgraded.type, "extension"); + + await extension.unload(); + await promiseShutdownManager(); +}); + +// add-on registers upgrade listener, and allows update. +add_task(async function delay_updates_complete() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: COMPLETE_ID, + update_url: `http://example.com/data/test_delay_updates_complete.json`, + }, + }, + }, + background() { + browser.runtime.onUpdateAvailable.addListener(details => { + browser.test.notifyPass("reload"); + browser.runtime.reload(); + }); + browser.test.sendMessage("ready"); + }, + }); + + await Promise.all([extension.startup(), extension.awaitMessage("ready")]); + + let addon = await promiseAddonByID(COMPLETE_ID); + Assert.notEqual(addon, null); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.name, "Generated extension"); + Assert.ok(addon.isCompatible); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + Assert.equal(addon.type, "extension"); + + let update = await promiseFindAddonUpdates(addon); + let install = update.updateAvailable; + + let promiseInstalled = promiseAddonEvent("onInstalled"); + await promiseCompleteAllInstalls([install]); + + await extension.awaitFinish("reload"); + + // addon upgrade has been allowed + let [addon_allowed] = await promiseInstalled; + await extension.awaitStartup(); + + Assert.notEqual(addon_allowed, null); + Assert.equal(addon_allowed.version, "2.0"); + Assert.equal(addon_allowed.name, "Delay Upgrade"); + Assert.ok(addon_allowed.isCompatible); + Assert.ok(!addon_allowed.appDisabled); + Assert.ok(addon_allowed.isActive); + Assert.equal(addon_allowed.type, "extension"); + + await new Promise(executeSoon); + + if (stageDir.exists()) { + do_throw( + "Staging directory should not exist for formerly-postponed extension" + ); + } + + await extension.unload(); + await promiseShutdownManager(); +}); + +// add-on registers upgrade listener, initially defers update then allows upgrade +add_task(async function delay_updates_defer() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: DEFER_ID, + update_url: `http://example.com/data/test_delay_updates_defer.json`, + }, + }, + }, + background() { + browser.runtime.onUpdateAvailable.addListener(details => { + // Upgrade will only proceed when "allow" message received. + browser.test.onMessage.addListener(msg => { + if (msg == "allow") { + browser.test.notifyPass("allowed"); + browser.runtime.reload(); + } else { + browser.test.fail(`wrong message: ${msg}`); + } + }); + browser.test.sendMessage("truly ready"); + }); + browser.test.sendMessage("ready"); + }, + }); + + await Promise.all([extension.startup(), extension.awaitMessage("ready")]); + + let addon = await promiseAddonByID(DEFER_ID); + Assert.notEqual(addon, null); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.name, "Generated extension"); + Assert.ok(addon.isCompatible); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + Assert.equal(addon.type, "extension"); + + let update = await promiseFindAddonUpdates(addon); + let install = update.updateAvailable; + + let promiseInstalled = promiseAddonEvent("onInstalled"); + await promiseCompleteAllInstalls([install]); + + Assert.equal(install.state, AddonManager.STATE_POSTPONED); + + // upgrade is initially postponed + let addon_postponed = await promiseAddonByID(DEFER_ID); + Assert.notEqual(addon_postponed, null); + Assert.equal(addon_postponed.version, "1.0"); + Assert.equal(addon_postponed.name, "Generated extension"); + Assert.ok(addon_postponed.isCompatible); + Assert.ok(!addon_postponed.appDisabled); + Assert.ok(addon_postponed.isActive); + Assert.equal(addon_postponed.type, "extension"); + + // add-on will not allow upgrade until message is received + await extension.awaitMessage("truly ready"); + extension.sendMessage("allow"); + await extension.awaitFinish("allowed"); + + // addon upgrade has been allowed + let [addon_allowed] = await promiseInstalled; + await extension.awaitStartup(); + + Assert.notEqual(addon_allowed, null); + Assert.equal(addon_allowed.version, "2.0"); + Assert.equal(addon_allowed.name, "Delay Upgrade"); + Assert.ok(addon_allowed.isCompatible); + Assert.ok(!addon_allowed.appDisabled); + Assert.ok(addon_allowed.isActive); + Assert.equal(addon_allowed.type, "extension"); + + await promiseRestartManager(); + + // restart changes nothing + addon_allowed = await promiseAddonByID(DEFER_ID); + await extension.awaitStartup(); + + Assert.notEqual(addon_allowed, null); + Assert.equal(addon_allowed.version, "2.0"); + Assert.equal(addon_allowed.name, "Delay Upgrade"); + Assert.ok(addon_allowed.isCompatible); + Assert.ok(!addon_allowed.appDisabled); + Assert.ok(addon_allowed.isActive); + Assert.equal(addon_allowed.type, "extension"); + + await extension.unload(); + await promiseShutdownManager(); +}); + +// add-on registers upgrade listener to deny update, completes after restart, +// even though the updated XPI is incompatible - the information returned +// by the update server defined in its manifest returns a compatible range +add_task(async function delay_updates_staged() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: STAGED_ID, + update_url: `http://example.com/data/test_delay_updates_staged.json`, + }, + }, + }, + background() { + browser.runtime.onUpdateAvailable.addListener(details => { + browser.test.sendMessage("denied"); + }); + browser.test.sendMessage("ready"); + }, + }); + + await Promise.all([extension.startup(), extension.awaitMessage("ready")]); + + let addon = await promiseAddonByID(STAGED_ID); + Assert.notEqual(addon, null); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.name, "Generated extension"); + Assert.ok(addon.isCompatible); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + Assert.equal(addon.type, "extension"); + + let update = await promiseFindAddonUpdates(addon); + let install = update.updateAvailable; + await promiseCompleteAllInstalls([install]); + + Assert.equal(install.state, AddonManager.STATE_POSTPONED); + + // upgrade is initially postponed + let addon_postponed = await promiseAddonByID(STAGED_ID); + Assert.notEqual(addon_postponed, null); + Assert.equal(addon_postponed.version, "1.0"); + Assert.equal(addon_postponed.name, "Generated extension"); + Assert.ok(addon_postponed.isCompatible); + Assert.ok(!addon_postponed.appDisabled); + Assert.ok(addon_postponed.isActive); + Assert.equal(addon_postponed.type, "extension"); + + // add-on reports an available upgrade, but denied it till next restart + await extension.awaitMessage("denied"); + + await promiseRestartManager(); + await extension.awaitStartup(); + + // add-on should have been updated during restart + let addon_upgraded = await promiseAddonByID(STAGED_ID); + Assert.notEqual(addon_upgraded, null); + Assert.equal(addon_upgraded.version, "2.0"); + Assert.equal(addon_upgraded.name, "Delay Upgrade"); + Assert.ok(addon_upgraded.isCompatible); + Assert.ok(!addon_upgraded.appDisabled); + Assert.ok(addon_upgraded.isActive); + Assert.equal(addon_upgraded.type, "extension"); + + await extension.unload(); + await promiseShutdownManager(); +}); + +// add-on registers upgrade listener to deny update, does not complete after +// restart, because the updated XPI is incompatible - there is no update server +// defined in its manifest, which could return a compatible range +add_task(async function delay_updates_staged_no_update_url() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: STAGED_NO_UPDATE_URL_ID, + update_url: `http://example.com/data/test_delay_updates_staged.json`, + }, + }, + }, + background() { + browser.runtime.onUpdateAvailable.addListener(details => { + browser.test.sendMessage("denied"); + }); + browser.test.sendMessage("ready"); + }, + }); + + await Promise.all([extension.startup(), extension.awaitMessage("ready")]); + + let addon = await promiseAddonByID(STAGED_NO_UPDATE_URL_ID); + Assert.notEqual(addon, null); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.name, "Generated extension"); + Assert.ok(addon.isCompatible); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + Assert.equal(addon.type, "extension"); + + let update = await promiseFindAddonUpdates(addon); + let install = update.updateAvailable; + await promiseCompleteAllInstalls([install]); + + Assert.equal(install.state, AddonManager.STATE_POSTPONED); + + // upgrade is initially postponed + let addon_postponed = await promiseAddonByID(STAGED_NO_UPDATE_URL_ID); + Assert.notEqual(addon_postponed, null); + Assert.equal(addon_postponed.version, "1.0"); + Assert.equal(addon_postponed.name, "Generated extension"); + Assert.ok(addon_postponed.isCompatible); + Assert.ok(!addon_postponed.appDisabled); + Assert.ok(addon_postponed.isActive); + Assert.equal(addon_postponed.type, "extension"); + + // add-on reports an available upgrade, but denied it till next restart + await extension.awaitMessage("denied"); + + await promiseRestartManager(); + await extension.awaitStartup(); + + // add-on should not have been updated during restart + let addon_upgraded = await promiseAddonByID(STAGED_NO_UPDATE_URL_ID); + Assert.notEqual(addon_upgraded, null); + Assert.equal(addon_upgraded.version, "1.0"); + Assert.equal(addon_upgraded.name, "Generated extension"); + Assert.ok(addon_upgraded.isCompatible); + Assert.ok(!addon_upgraded.appDisabled); + Assert.ok(addon_upgraded.isActive); + Assert.equal(addon_upgraded.type, "extension"); + + await extension.unload(); + await promiseShutdownManager(); +}); + +// browser.runtime.reload() without a pending upgrade should just reload. +add_task(async function runtime_reload() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: NOUPDATE_ID, + update_url: `http://example.com/data/test_no_update.json`, + }, + }, + }, + background() { + browser.test.onMessage.addListener(msg => { + if (msg == "reload") { + browser.runtime.reload(); + } else { + browser.test.fail(`wrong message: ${msg}`); + } + }); + browser.test.sendMessage("ready"); + }, + }); + + await Promise.all([extension.startup(), extension.awaitMessage("ready")]); + + let addon = await promiseAddonByID(NOUPDATE_ID); + Assert.notEqual(addon, null); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.name, "Generated extension"); + Assert.ok(addon.isCompatible); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + Assert.equal(addon.type, "extension"); + + await promiseFindAddonUpdates(addon); + + extension.sendMessage("reload"); + // Wait for extension to restart, to make sure reload works. + await AddonTestUtils.promiseWebExtensionStartup(NOUPDATE_ID); + await extension.awaitMessage("ready"); + + addon = await promiseAddonByID(NOUPDATE_ID); + Assert.notEqual(addon, null); + Assert.equal(addon.version, "1.0"); + Assert.equal(addon.name, "Generated extension"); + Assert.ok(addon.isCompatible); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + Assert.equal(addon.type, "extension"); + + await extension.unload(); + await promiseShutdownManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_dependencies.js b/toolkit/mozapps/extensions/test/xpcshell/test_dependencies.js new file mode 100644 index 0000000000..476c9e0595 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dependencies.js @@ -0,0 +1,140 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + +const ADDONS = [ + { + id: "addon1@experiments.addons.mozilla.org", + dependencies: ["experiments.addon2"], + }, + { + id: "addon2@experiments.addons.mozilla.org", + dependencies: ["experiments.addon3"], + }, + { + id: "addon3@experiments.addons.mozilla.org", + }, + { + id: "addon4@experiments.addons.mozilla.org", + }, + { + id: "addon5@experiments.addons.mozilla.org", + dependencies: ["experiments.addon2"], + }, +]; + +let addonFiles = []; + +let events = []; + +function promiseAddonStartup(id) { + return new Promise(resolve => { + const onBootstrapMethod = (event, { method, params }) => { + if (method == "startup" && params.id == id) { + AddonTestUtils.off("bootstrap-method", onBootstrapMethod); + resolve(); + } + }; + + AddonTestUtils.on("bootstrap-method", onBootstrapMethod); + }); +} + +add_task(async function setup() { + await promiseStartupManager(); + + const onBootstrapMethod = (event, { method, params }) => { + if (method == "startup" || method == "shutdown") { + events.push([method, params.id]); + } + }; + + AddonTestUtils.on("bootstrap-method", onBootstrapMethod); + registerCleanupFunction(() => { + AddonTestUtils.off("bootstrap-method", onBootstrapMethod); + }); + + for (let addon of ADDONS) { + let manifest = { + browser_specific_settings: { gecko: { id: addon.id } }, + permissions: addon.dependencies, + }; + + addonFiles.push(await createTempWebExtensionFile({ manifest })); + } +}); + +add_task(async function () { + deepEqual(events, [], "Should have no events"); + + await promiseInstallFile(addonFiles[3]); + + deepEqual(events, [["startup", ADDONS[3].id]]); + + events.length = 0; + + await promiseInstallFile(addonFiles[0]); + deepEqual(events, [], "Should have no events"); + + await promiseInstallFile(addonFiles[1]); + deepEqual(events, [], "Should have no events"); + + await Promise.all([ + promiseInstallFile(addonFiles[2]), + promiseAddonStartup(ADDONS[0].id), + ]); + + deepEqual(events, [ + ["startup", ADDONS[2].id], + ["startup", ADDONS[1].id], + ["startup", ADDONS[0].id], + ]); + + events.length = 0; + + await Promise.all([ + promiseInstallFile(addonFiles[2]), + promiseAddonStartup(ADDONS[0].id), + ]); + + deepEqual(events, [ + ["shutdown", ADDONS[0].id], + ["shutdown", ADDONS[1].id], + ["shutdown", ADDONS[2].id], + + ["startup", ADDONS[2].id], + ["startup", ADDONS[1].id], + ["startup", ADDONS[0].id], + ]); + + events.length = 0; + + await promiseInstallFile(addonFiles[4]); + + deepEqual(events, [["startup", ADDONS[4].id]]); + + events.length = 0; + + await promiseRestartManager(); + + deepEqual(events, [ + ["shutdown", ADDONS[4].id], + ["shutdown", ADDONS[3].id], + ["shutdown", ADDONS[0].id], + ["shutdown", ADDONS[1].id], + ["shutdown", ADDONS[2].id], + + ["startup", ADDONS[2].id], + ["startup", ADDONS[1].id], + ["startup", ADDONS[0].id], + ["startup", ADDONS[3].id], + ["startup", ADDONS[4].id], + ]); + + await promiseShutdownManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js new file mode 100644 index 0000000000..847d519036 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js @@ -0,0 +1,233 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +XPCOMUtils.defineLazyServiceGetter( + this, + "spellCheck", + "@mozilla.org/spellchecker/engine;1", + "mozISpellCheckingEngine" +); + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "61", "61"); + + // Initialize the URLPreloader so that we can load the built-in + // add-ons list, which contains the list of built-in dictionaries. + AddonTestUtils.initializeURLPreloader(); + + await promiseStartupManager(); + + // Starts collecting the Addon Manager Telemetry events. + AddonTestUtils.hookAMTelemetryEvents(); +}); + +add_task( + { + // We need to enable this pref because some assertions verify that + // `installOrigins` is collected in some Telemetry events. + pref_set: [["extensions.install_origins.enabled", true]], + }, + async function test_validation() { + await Assert.rejects( + promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { + gecko: { id: "en-US-no-dic@dictionaries.mozilla.org" }, + }, + dictionaries: { + "en-US": "en-US.dic", + }, + }, + }), + /Expected file to be downloaded for install/ + ); + + await Assert.rejects( + promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { + gecko: { id: "en-US-no-aff@dictionaries.mozilla.org" }, + }, + dictionaries: { + "en-US": "en-US.dic", + }, + }, + + files: { + "en-US.dic": "", + }, + }), + /Expected file to be downloaded for install/ + ); + + let addon = await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { + gecko: { id: "en-US-1@dictionaries.mozilla.org" }, + }, + dictionaries: { + "en-US": "en-US.dic", + }, + }, + + files: { + "en-US.dic": "", + "en-US.aff": "", + }, + }); + + let addon2 = await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { + gecko: { id: "en-US-2@dictionaries.mozilla.org" }, + }, + dictionaries: { + "en-US": "dictionaries/en-US.dic", + }, + }, + + files: { + "dictionaries/en-US.dic": "", + "dictionaries/en-US.aff": "", + }, + }); + + await addon.uninstall(); + await addon2.uninstall(); + + let amEvents = AddonTestUtils.getAMTelemetryEvents(); + + let amInstallEvents = amEvents + .filter(evt => evt.method === "install") + .map(evt => { + const { object, extra } = evt; + return { object, extra }; + }); + + Assert.deepEqual( + amInstallEvents.filter(evt => evt.object === "unknown"), + [ + { + object: "unknown", + extra: { + step: "started", + error: "ERROR_CORRUPT_FILE", + install_origins: "0", + }, + }, + { + object: "unknown", + extra: { + step: "started", + error: "ERROR_CORRUPT_FILE", + install_origins: "0", + }, + }, + ], + "Got the expected install telemetry events for the corrupted dictionaries" + ); + + Assert.deepEqual( + amInstallEvents.filter(evt => evt.extra.addon_id === addon.id), + [ + { + object: "dictionary", + extra: { step: "started", addon_id: addon.id, install_origins: "0" }, + }, + { + object: "dictionary", + extra: { + step: "completed", + addon_id: addon.id, + install_origins: "0", + }, + }, + ], + "Got the expected install telemetry events for the first installed dictionary" + ); + + Assert.deepEqual( + amInstallEvents.filter(evt => evt.extra.addon_id === addon2.id), + [ + { + object: "dictionary", + extra: { step: "started", addon_id: addon2.id, install_origins: "0" }, + }, + { + object: "dictionary", + extra: { + step: "completed", + addon_id: addon2.id, + install_origins: "0", + }, + }, + ], + "Got the expected install telemetry events for the second installed dictionary" + ); + + let amUninstallEvents = amEvents + .filter(evt => evt.method === "uninstall") + .map(evt => { + const { object, value } = evt; + return { object, value }; + }); + + Assert.deepEqual( + amUninstallEvents, + [ + { object: "dictionary", value: addon.id }, + { object: "dictionary", value: addon2.id }, + ], + "Got the expected uninstall telemetry events" + ); + } +); + +const WORD = "Flehgragh"; + +add_task(async function test_registration() { + spellCheck.dictionaries = ["en-US"]; + + ok(!spellCheck.check(WORD), "Word should not pass check before add-on loads"); + + let addon = await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { + gecko: { id: "en-US@dictionaries.mozilla.org" }, + }, + dictionaries: { + "en-US": "en-US.dic", + }, + }, + + files: { + "en-US.dic": `2\n${WORD}\nnativ/A\n`, + "en-US.aff": ` +SFX A Y 1 +SFX A 0 en [^elr] + `, + }, + }); + + ok( + spellCheck.check(WORD), + "Word should pass check while add-on load is loaded" + ); + ok(spellCheck.check("nativen"), "Words should have correct affixes"); + + await addon.uninstall(); + + await new Promise(executeSoon); + + ok( + !spellCheck.check(WORD), + "Word should not pass check after add-on unloads" + ); +}); + +add_task(function teardown_telemetry_events() { + // Ignore any additional telemetry events collected in this file. + AddonTestUtils.getAMTelemetryEvents(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js new file mode 100644 index 0000000000..61231160d8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that add-ons distributed with the application get installed +// correctly + +// Allow distributed add-ons to install +Services.prefs.setBoolPref("extensions.installDistroAddons", true); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); +const distroDir = gProfD.clone(); +distroDir.append("distribution"); +distroDir.append("extensions"); +registerDirectory("XREAppDist", distroDir.parent); + +async function setOldModificationTime() { + // Make sure the installed extension has an old modification time so any + // changes will be detected + await promiseShutdownManager(); + let extension = gProfD.clone(); + extension.append("extensions"); + extension.append(`${ID}.xpi`); + setExtensionModifiedTime(extension, Date.now() - MAKE_FILE_OLD_DIFFERENCE); + await promiseStartupManager(); +} + +const ID = "addon@tests.mozilla.org"; + +async function writeDistroAddon(version) { + let xpi = await createTempWebExtensionFile({ + manifest: { + version, + browser_specific_settings: { gecko: { id: ID } }, + }, + }); + xpi.copyTo(distroDir, `${ID}.xpi`); +} + +// Tests that on the first startup the add-on gets installed +add_task(async function run_test_1() { + await writeDistroAddon("1.0"); + await promiseStartupManager(); + + let a1 = await AddonManager.getAddonByID(ID); + Assert.notEqual(a1, null); + Assert.equal(a1.version, "1.0"); + Assert.ok(a1.isActive); + Assert.equal(a1.scope, AddonManager.SCOPE_PROFILE); + Assert.ok(!a1.foreignInstall); +}); + +// Tests that starting with a newer version in the distribution dir doesn't +// install it yet +add_task(async function run_test_2() { + await setOldModificationTime(); + + await writeDistroAddon("2.0"); + await promiseRestartManager(); + + let a1 = await AddonManager.getAddonByID(ID); + Assert.notEqual(a1, null); + Assert.equal(a1.version, "1.0"); + Assert.ok(a1.isActive); + Assert.equal(a1.scope, AddonManager.SCOPE_PROFILE); +}); + +// Test that an app upgrade installs the newer version +add_task(async function run_test_3() { + await promiseRestartManager("2"); + + let a1 = await AddonManager.getAddonByID(ID); + Assert.notEqual(a1, null); + Assert.equal(a1.version, "2.0"); + Assert.ok(a1.isActive); + Assert.equal(a1.scope, AddonManager.SCOPE_PROFILE); + Assert.ok(!a1.foreignInstall); +}); + +// Test that an app upgrade doesn't downgrade the extension +add_task(async function run_test_4() { + await setOldModificationTime(); + + await writeDistroAddon("1.0"); + await promiseRestartManager("3"); + + let a1 = await AddonManager.getAddonByID(ID); + Assert.notEqual(a1, null); + Assert.equal(a1.version, "2.0"); + Assert.ok(a1.isActive); + Assert.equal(a1.scope, AddonManager.SCOPE_PROFILE); +}); + +// Tests that after uninstalling a restart doesn't re-install the extension +add_task(async function run_test_5() { + let a1 = await AddonManager.getAddonByID(ID); + await a1.uninstall(); + + await promiseRestartManager(); + + let a1_2 = await AddonManager.getAddonByID(ID); + Assert.equal(a1_2, null); +}); + +// Tests that upgrading the application still doesn't re-install the uninstalled +// extension +add_task(async function run_test_6() { + await promiseRestartManager("4"); + + let a1 = await AddonManager.getAddonByID(ID); + Assert.equal(a1, null); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_distribution_langpack.js b/toolkit/mozapps/extensions/test/xpcshell/test_distribution_langpack.js new file mode 100644 index 0000000000..0e594d60ec --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_distribution_langpack.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that add-ons distributed with the application in +// langauge subdirectories correctly get installed + +// Allow distributed add-ons to install +Services.prefs.setBoolPref("extensions.installDistroAddons", true); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); +const distroDir = gProfD.clone(); +distroDir.append("distribution"); +distroDir.append("extensions"); +registerDirectory("XREAppDist", distroDir.parent); +const enUSDistroDir = distroDir.clone(); +enUSDistroDir.append("locale-en-US"); +const deDEDistroDir = distroDir.clone(); +deDEDistroDir.append("locale-de-DE"); +const esESDistroDir = distroDir.clone(); +esESDistroDir.append("locale-es-ES"); + +const enUSID = "addon-en-US@tests.mozilla.org"; +const deDEID = "addon-de-DE@tests.mozilla.org"; +const esESID = "addon-es-ES@tests.mozilla.org"; + +async function writeDistroAddons(version) { + let xpi = await createTempWebExtensionFile({ + manifest: { + version, + browser_specific_settings: { gecko: { id: enUSID } }, + }, + }); + xpi.copyTo(enUSDistroDir, `${enUSID}.xpi`); + + xpi = await createTempWebExtensionFile({ + manifest: { + version, + browser_specific_settings: { gecko: { id: deDEID } }, + }, + }); + xpi.copyTo(deDEDistroDir, `${deDEID}.xpi`); + + xpi = await createTempWebExtensionFile({ + manifest: { + version, + browser_specific_settings: { gecko: { id: esESID } }, + }, + }); + xpi.copyTo(esESDistroDir, `${esESID}.xpi`); +} + +add_task(async function setup() { + await writeDistroAddons("1.0"); +}); + +// Tests that on the first startup the requested locale +// add-on gets installed, and others don't. +add_task(async function run_locale_test() { + Services.locale.availableLocales = ["de-DE", "en-US"]; + Services.locale.requestedLocales = ["de-DE"]; + + Assert.equal(Services.locale.requestedLocale, "de-DE"); + + await promiseStartupManager(); + + let a1 = await AddonManager.getAddonByID(deDEID); + Assert.notEqual(a1, null); + Assert.equal(a1.version, "1.0"); + Assert.ok(a1.isActive); + Assert.equal(a1.scope, AddonManager.SCOPE_PROFILE); + Assert.ok(!a1.foreignInstall); + + let a2 = await AddonManager.getAddonByID(enUSID); + Assert.equal(a2, null); + + let a3 = await AddonManager.getAddonByID(esESID); + Assert.equal(a3, null); + + await a1.uninstall(); + await promiseShutdownManager(); +}); + +// Tests that on the first startup the correct fallback locale +// add-on gets installed, and others don't. +add_task(async function run_fallback_test() { + Services.locale.availableLocales = ["es-ES", "en-US"]; + Services.locale.requestedLocales = ["es-UY"]; + + Assert.equal(Services.locale.requestedLocale, "es-UY"); + + await promiseStartupManager(); + + let a1 = await AddonManager.getAddonByID(esESID); + Assert.notEqual(a1, null); + Assert.equal(a1.version, "1.0"); + Assert.ok(a1.isActive); + Assert.equal(a1.scope, AddonManager.SCOPE_PROFILE); + Assert.ok(!a1.foreignInstall); + + let a2 = await AddonManager.getAddonByID(enUSID); + Assert.equal(a2, null); + + let a3 = await AddonManager.getAddonByID(deDEID); + Assert.equal(a3, null); + + await a1.uninstall(); + await promiseShutdownManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_embedderDisabled.js b/toolkit/mozapps/extensions/test/xpcshell/test_embedderDisabled.js new file mode 100644 index 0000000000..943b3cf0c3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_embedderDisabled.js @@ -0,0 +1,124 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const ADDON_ID = "embedder-disabled@tests.mozilla.org"; +const PREF_IS_EMBEDDED = "extensions.isembedded"; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49"); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref(PREF_IS_EMBEDDED); +}); + +async function installExtension() { + return promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { gecko: { id: ADDON_ID } }, + }, + }); +} + +add_task(async function test_setup() { + await promiseStartupManager(); +}); + +add_task(async function embedder_disabled_while_not_embedding() { + const addon = await installExtension(); + let exceptionThrown = false; + try { + await addon.setEmbedderDisabled(true); + } catch (exception) { + exceptionThrown = true; + } + + equal(exceptionThrown, true); + + // Verify that the addon is not affected + equal(addon.isActive, true); + equal(addon.embedderDisabled, undefined); + + await addon.uninstall(); +}); + +add_task(async function unset_embedder_disabled_while_not_embedding() { + Services.prefs.setBoolPref(PREF_IS_EMBEDDED, true); + + const addon = await installExtension(); + await addon.setEmbedderDisabled(true); + + // Verify the addon is not active anymore + equal(addon.isActive, false); + equal(addon.embedderDisabled, true); + + Services.prefs.setBoolPref(PREF_IS_EMBEDDED, false); + + // Verify that embedder disabled cannot be read if not embedding + equal(addon.embedderDisabled, undefined); + + await addon.disable(); + await addon.enable(); + + // Verify that embedder disabled can be removed + equal(addon.isActive, true); + equal(addon.embedderDisabled, undefined); + + await addon.uninstall(); +}); + +add_task(async function embedder_disabled_while_embedding() { + Services.prefs.setBoolPref(PREF_IS_EMBEDDED, true); + + const addon = await installExtension(); + await addon.setEmbedderDisabled(true); + + // Verify the addon is not active anymore + equal(addon.embedderDisabled, true); + equal(addon.isActive, false); + + await addon.setEmbedderDisabled(false); + + // Verify that the addon is now enabled again + equal(addon.isActive, true); + equal(addon.embedderDisabled, false); + await addon.uninstall(); + + Services.prefs.setBoolPref(PREF_IS_EMBEDDED, false); +}); + +add_task(async function embedder_disabled_while_user_disabled() { + Services.prefs.setBoolPref(PREF_IS_EMBEDDED, true); + + const addon = await installExtension(); + await addon.disable(); + + // Verify that the addon is userDisabled + equal(addon.isActive, false); + equal(addon.userDisabled, true); + equal(addon.embedderDisabled, false); + + await addon.setEmbedderDisabled(true); + + // Verify that the addon can be userDisabled and embedderDisabled + equal(addon.isActive, false); + equal(addon.userDisabled, true); + equal(addon.embedderDisabled, true); + + await addon.setEmbedderDisabled(false); + + // Verify that unsetting embedderDisabled doesn't enable the addon + equal(addon.isActive, false); + equal(addon.userDisabled, true); + equal(addon.embedderDisabled, false); + + await addon.enable(); + + // Verify that the addon can be enabled after unsetting userDisabled + equal(addon.isActive, true); + equal(addon.userDisabled, false); + equal(addon.embedderDisabled, false); + + await addon.uninstall(); + + Services.prefs.setBoolPref(PREF_IS_EMBEDDED, false); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_error.js b/toolkit/mozapps/extensions/test/xpcshell/test_error.js new file mode 100644 index 0000000000..ee972f222e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_error.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that various error conditions are handled correctly + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + await promiseStartupManager(); +}); + +// Checks that a local file validates ok +add_task(async function run_test_1() { + let xpi = await createTempWebExtensionFile({}); + let install = await AddonManager.getInstallForFile(xpi); + Assert.notEqual(install, null); + Assert.equal(install.state, AddonManager.STATE_DOWNLOADED); + Assert.equal(install.error, 0); + + install.cancel(); +}); + +// Checks that a corrupt file shows an error +add_task(async function run_test_2() { + let xpi = AddonTestUtils.allocTempXPIFile(); + await IOUtils.writeUTF8(xpi.path, "this is not a zip file"); + + let install = await AddonManager.getInstallForFile(xpi); + Assert.notEqual(install, null); + Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED); + Assert.equal(install.error, AddonManager.ERROR_CORRUPT_FILE); +}); + +// Checks that an empty file shows an error +add_task(async function run_test_3() { + let xpi = await AddonTestUtils.createTempXPIFile({}); + let install = await AddonManager.getInstallForFile(xpi); + Assert.notEqual(install, null); + Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED); + Assert.equal(install.error, AddonManager.ERROR_CORRUPT_FILE); +}); + +// Checks that a file that doesn't match its hash shows an error +add_task(async function run_test_4() { + let xpi = await createTempWebExtensionFile({}); + let url = Services.io.newFileURI(xpi).spec; + let install = await AddonManager.getInstallForURL(url, { hash: "sha1:foo" }); + Assert.notEqual(install, null); + Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED); + Assert.equal(install.error, AddonManager.ERROR_INCORRECT_HASH); +}); + +// Checks that a file that doesn't exist shows an error +add_task(async function run_test_5() { + let file = do_get_file("data"); + file.append("missing.xpi"); + let install = await AddonManager.getInstallForFile(file); + Assert.notEqual(install, null); + Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED); + Assert.equal(install.error, AddonManager.ERROR_NETWORK_FAILURE); +}); + +// Checks that an add-on with an illegal ID shows an error +add_task(async function run_test_6() { + let xpi = await createTempWebExtensionFile({ + manifest: { + browser_specific_settings: { gecko: { id: "invalid" } }, + }, + }); + let install = await AddonManager.getInstallForFile(xpi); + Assert.notEqual(install, null); + Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED); + Assert.equal(install.error, AddonManager.ERROR_CORRUPT_FILE); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js b/toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js new file mode 100644 index 0000000000..9fbff4efe1 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js @@ -0,0 +1,223 @@ +"use strict"; + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "48", "48"); + await promiseStartupManager(); +}); + +/* eslint-disable no-undef */ +// Shared background function for getSelf tests +function backgroundGetSelf() { + browser.management.getSelf().then( + extInfo => { + let url = browser.runtime.getURL("*"); + extInfo.hostPermissions = extInfo.hostPermissions.filter(i => i != url); + + // Internal permissions are currently part of the permissions included + // in the management.getSelf results, and in non release channels + // any temporary installed extension is recognized as privileged + // and some internal permission would be added automatically. + // + // TODO(Bug 1713344): this may become unnecessary if we filter out + // the internal permissions from the management API results. + extInfo.permissions = extInfo.permissions.filter( + i => !i.startsWith("internal:") + ); + + extInfo.url = browser.runtime.getURL(""); + browser.test.sendMessage("management-getSelf", extInfo); + }, + error => { + browser.test.notifyFail(`getSelf rejected with error: ${error}`); + } + ); +} +/* eslint-enable no-undef */ + +add_task(async function test_management_get_self_complete() { + const id = "get_self_test_complete@tests.mozilla.com"; + const permissions = ["management", "cookies"]; + const hostPermissions = ["*://example.org/*", "https://foo.example.org/*"]; + + let manifest = { + browser_specific_settings: { + gecko: { + id, + update_url: "https://updates.mozilla.com/", + }, + }, + name: "test extension name", + short_name: "test extension short name", + description: "test extension description", + version: "1.0", + homepage_url: "http://www.example.com/", + options_ui: { + page: "get_self_options.html", + }, + icons: { + 16: "icons/icon-16.png", + 48: "icons/icon-48.png", + }, + permissions: [...permissions, ...hostPermissions], + }; + + let extension = ExtensionTestUtils.loadExtension({ + manifest, + background: backgroundGetSelf, + useAddonManager: "temporary", + }); + await extension.startup(); + let extInfo = await extension.awaitMessage("management-getSelf"); + + equal(extInfo.id, id, "getSelf returned the expected id"); + equal( + extInfo.installType, + "development", + "getSelf returned the expected installType" + ); + for (let prop of ["name", "description", "version"]) { + equal( + extInfo[prop], + manifest[prop], + `getSelf returned the expected ${prop}` + ); + } + equal( + extInfo.shortName, + manifest.short_name, + "getSelf returned the expected shortName" + ); + equal( + extInfo.mayDisable, + true, + "getSelf returned the expected value for mayDisable" + ); + equal( + extInfo.enabled, + true, + "getSelf returned the expected value for enabled" + ); + equal( + extInfo.homepageUrl, + manifest.homepage_url, + "getSelf returned the expected homepageUrl" + ); + equal( + extInfo.updateUrl, + manifest.browser_specific_settings.gecko.update_url, + "getSelf returned the expected updateUrl" + ); + ok( + extInfo.optionsUrl.endsWith(manifest.options_ui.page), + "getSelf returned the expected optionsUrl" + ); + for (let [index, size] of Object.keys(manifest.icons).sort().entries()) { + let iconUrl = `${extInfo.url}${manifest.icons[size]}`; + equal( + extInfo.icons[index].size, + +size, + "getSelf returned the expected icon size" + ); + equal( + extInfo.icons[index].url, + iconUrl, + "getSelf returned the expected icon url" + ); + } + deepEqual( + extInfo.permissions.sort(), + permissions.sort(), + "getSelf returned the expected permissions" + ); + deepEqual( + extInfo.hostPermissions.sort(), + hostPermissions.sort(), + "getSelf returned the expected hostPermissions" + ); + equal( + extInfo.installType, + "development", + "getSelf returned the expected installType" + ); + await extension.unload(); +}); + +add_task(async function test_management_get_self_minimal() { + const id = "get_self_test_minimal@tests.mozilla.com"; + + let manifest = { + browser_specific_settings: { + gecko: { + id, + }, + }, + name: "test extension name", + version: "1.0", + }; + + let extension = ExtensionTestUtils.loadExtension({ + manifest, + background: backgroundGetSelf, + useAddonManager: "temporary", + }); + await extension.startup(); + let extInfo = await extension.awaitMessage("management-getSelf"); + + equal(extInfo.id, id, "getSelf returned the expected id"); + equal( + extInfo.installType, + "development", + "getSelf returned the expected installType" + ); + for (let prop of ["name", "version"]) { + equal( + extInfo[prop], + manifest[prop], + `getSelf returned the expected ${prop}` + ); + } + for (let prop of ["shortName", "description", "optionsUrl"]) { + equal(extInfo[prop], "", `getSelf returned the expected ${prop}`); + } + for (let prop of ["homepageUrl", " updateUrl", "icons"]) { + equal( + Reflect.getOwnPropertyDescriptor(extInfo, prop), + undefined, + `getSelf did not return a ${prop} property` + ); + } + for (let prop of ["permissions", "hostPermissions"]) { + deepEqual(extInfo[prop], [], `getSelf returned the expected ${prop}`); + } + await extension.unload(); +}); + +add_task(async function test_management_get_self_permanent() { + const id = "get_self_test_permanent@tests.mozilla.com"; + + let manifest = { + browser_specific_settings: { + gecko: { + id, + }, + }, + name: "test extension name", + version: "1.0", + }; + + let extension = ExtensionTestUtils.loadExtension({ + manifest, + background: backgroundGetSelf, + useAddonManager: "permanent", + }); + await extension.startup(); + let extInfo = await extension.awaitMessage("management-getSelf"); + + equal(extInfo.id, id, "getSelf returned the expected id"); + equal( + extInfo.installType, + "normal", + "getSelf returned the expected installType" + ); + await extension.unload(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js new file mode 100644 index 0000000000..c72737d4fe --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js @@ -0,0 +1,327 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that various operations with file pointers work and do not affect the +// source files + +const ID1 = "addon1@tests.mozilla.org"; +const ID2 = "addon2@tests.mozilla.org"; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); +profileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + +const sourceDir = gProfD.clone(); +sourceDir.append("source"); + +function promiseWriteWebExtension(path, data) { + let files = ExtensionTestCommon.generateFiles(data); + return AddonTestUtils.promiseWriteFilesToDir(path, files); +} + +function promiseWritePointer(aId, aName) { + let path = PathUtils.join(profileDir.path, aName || aId); + + let target = PathUtils.join(sourceDir.path, do_get_expected_addon_name(aId)); + + return IOUtils.writeUTF8(path, target); +} + +function promiseWriteRelativePointer(aId, aName) { + let path = PathUtils.join(profileDir.path, aName || aId); + + let absTarget = sourceDir.clone(); + absTarget.append(do_get_expected_addon_name(aId)); + + let relTarget = absTarget.getRelativeDescriptor(profileDir); + + return IOUtils.writeUTF8(path, relTarget); +} + +add_task(async function setup() { + ok(TEST_UNPACKED, "Pointer files only work with unpacked directories"); + + // Unpacked extensions are never signed, so this can only run with + // signature checks disabled. + Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); +}); + +// Tests that installing a new add-on by pointer works +add_task(async function test_new_pointer_install() { + let target = PathUtils.join(sourceDir.path, ID1); + await promiseWriteWebExtension(target, { + manifest: { + version: "1.0", + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + await promiseWritePointer(ID1); + await promiseStartupManager(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "1.0"); + + let file = getAddonFile(addon); + equal(file.parent.path, sourceDir.path); + + let rootUri = do_get_addon_root_uri(sourceDir, ID1); + let uri = addon.getResourceURI(); + equal(uri.spec, rootUri); + + // Check that upgrade is disabled for addons installed by file-pointers. + equal(addon.permissions & AddonManager.PERM_CAN_UPGRADE, 0); +}); + +// Tests that installing the addon from some other source doesn't clobber +// the original sources +add_task(async function test_addon_over_pointer() { + let xpi = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "2.0", + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + + let install = await AddonManager.getInstallForFile( + xpi, + "application/x-xpinstall" + ); + await install.install(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "2.0"); + + let url = addon.getResourceURI(); + if (url instanceof Ci.nsIJARURI) { + url = url.JARFile; + } + let { file } = url.QueryInterface(Ci.nsIFileURL); + equal(file.parent.path, profileDir.path); + + let rootUri = do_get_addon_root_uri(profileDir, ID1); + let uri = addon.getResourceURI(); + equal(uri.spec, rootUri); + + let source = sourceDir.clone(); + source.append(ID1); + ok(source.exists()); + + await addon.uninstall(); +}); + +// Tests that uninstalling doesn't clobber the original sources +add_task(async function test_uninstall_pointer() { + await promiseWritePointer(ID1); + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "1.0"); + + await addon.uninstall(); + + let source = sourceDir.clone(); + source.append(ID1); + ok(source.exists()); +}); + +// Tests that misnaming a pointer doesn't clobber the sources +add_task(async function test_bad_pointer() { + await promiseWritePointer(ID2, ID1); + + let [a1, a2] = await AddonManager.getAddonsByIDs([ID1, ID2]); + equal(a1, null); + equal(a2, null); + + let source = sourceDir.clone(); + source.append(ID1); + ok(source.exists()); + + let pointer = profileDir.clone(); + pointer.append(ID2); + ok(!pointer.exists()); +}); + +// Tests that changing the ID of an existing add-on doesn't clobber the sources +add_task(async function test_bad_pointer_id() { + let dir = sourceDir.clone(); + dir.append(ID1); + + // Make sure the modification time changes enough to be detected. + setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000); + await promiseWritePointer(ID1); + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "1.0"); + + await promiseWriteWebExtension(dir.path, { + manifest: { + version: "1.0", + browser_specific_settings: { gecko: { id: ID2 } }, + }, + }); + setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000); + + await promiseRestartManager(); + + let [a1, a2] = await AddonManager.getAddonsByIDs([ID1, ID2]); + equal(a1, null); + equal(a2, null); + + let source = sourceDir.clone(); + source.append(ID1); + ok(source.exists()); + + let pointer = profileDir.clone(); + pointer.append(ID1); + ok(!pointer.exists()); +}); + +// Removing the pointer file should uninstall the add-on +add_task(async function test_remove_pointer() { + let dir = sourceDir.clone(); + dir.append(ID1); + + await promiseWriteWebExtension(dir.path, { + manifest: { + version: "1.0", + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + + setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000); + await promiseWritePointer(ID1); + + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "1.0"); + + let pointer = profileDir.clone(); + pointer.append(ID1); + pointer.remove(false); + + await promiseRestartManager(); + + addon = await AddonManager.getAddonByID(ID1); + equal(addon, null); +}); + +// Removing the pointer file and replacing it with a directory should work +add_task(async function test_replace_pointer() { + await promiseWritePointer(ID1); + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "1.0"); + + let pointer = profileDir.clone(); + pointer.append(ID1); + pointer.remove(false); + + await promiseWriteWebExtension(PathUtils.join(profileDir.path, ID1), { + manifest: { + version: "2.0", + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + + await promiseRestartManager(); + + addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "2.0"); + + await addon.uninstall(); +}); + +// Changes to the source files should be detected +add_task(async function test_change_pointer_sources() { + await promiseWritePointer(ID1); + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "1.0"); + + let dir = sourceDir.clone(); + dir.append(ID1); + await promiseWriteWebExtension(dir.path, { + manifest: { + version: "2.0", + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000); + + await promiseRestartManager(); + + addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "2.0"); + + await addon.uninstall(); +}); + +// Removing the add-on the pointer file points at should uninstall the add-on +add_task(async function test_remove_pointer_target() { + let target = PathUtils.join(sourceDir.path, ID1); + await promiseWriteWebExtension(target, { + manifest: { + version: "1.0", + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + await promiseWritePointer(ID1); + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID(ID1); + notEqual(addon, null); + equal(addon.version, "1.0"); + + await IOUtils.remove(target, { recursive: true }); + + await promiseRestartManager(); + + addon = await AddonManager.getAddonByID(ID1); + equal(addon, null); + + let pointer = profileDir.clone(); + pointer.append(ID1); + ok(!pointer.exists()); +}); + +// Tests that installing a new add-on by pointer with a relative path works +add_task(async function test_new_relative_pointer() { + let target = PathUtils.join(sourceDir.path, ID1); + await promiseWriteWebExtension(target, { + manifest: { + version: "1.0", + browser_specific_settings: { gecko: { id: ID1 } }, + }, + }); + await promiseWriteRelativePointer(ID1); + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID(ID1); + equal(addon.version, "1.0"); + + let { file } = addon.getResourceURI().QueryInterface(Ci.nsIFileURL); + equal(file.parent.path, sourceDir.path); + + let rootUri = do_get_addon_root_uri(sourceDir, ID1); + let uri = addon.getResourceURI(); + equal(uri.spec, rootUri); + + // Check that upgrade is disabled for addons installed by file-pointers. + equal(addon.permissions & AddonManager.PERM_CAN_UPGRADE, 0); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_general.js b/toolkit/mozapps/extensions/test/xpcshell/test_general.js new file mode 100644 index 0000000000..896e69d6f8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_general.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This just verifies that the EM can actually startup and shutdown a few times +// without any errors + +// We have to look up how many add-ons are present since there will be plugins +// etc. detected +var gCount; + +async function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + await promiseStartupManager(); + let list = await AddonManager.getAddonsByTypes(null); + gCount = list.length; + + executeSoon(run_test_1); +} + +async function run_test_1() { + await promiseRestartManager(); + + let addons = await AddonManager.getAddonsByTypes(null); + Assert.equal(gCount, addons.length); + + executeSoon(run_test_2); +} + +async function run_test_2() { + await promiseShutdownManager(); + + await promiseStartupManager(); + + let addons = await AddonManager.getAddonsByTypes(null); + Assert.equal(gCount, addons.length); + + executeSoon(run_test_3); +} + +async function run_test_3() { + await promiseRestartManager(); + + let addons = await AddonManager.getAddonsByTypes(null); + Assert.equal(gCount, addons.length); + do_test_finished(); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_getInstallSourceFromHost.js b/toolkit/mozapps/extensions/test/xpcshell/test_getInstallSourceFromHost.js new file mode 100644 index 0000000000..1f9b65ca85 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_getInstallSourceFromHost.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(function test_getInstallSourceFromHost_helpers() { + const test_hostname = + AppConstants.MOZ_APP_NAME !== "thunderbird" + ? "addons.allizom.org" + : "addons-stage.thunderbird.net"; + + const sourceHostTestCases = [ + { + host: test_hostname, + installSourceFromHost: "test-host", + }, + { + host: "addons.mozilla.org", + installSourceFromHost: "amo", + }, + { + host: "discovery.addons.mozilla.org", + installSourceFromHost: "disco", + }, + { + host: "about:blank", + installSourceFromHost: "unknown", + }, + { + host: "fake-extension-uuid", + installSourceFromHost: "unknown", + }, + { + host: null, + installSourceFromHost: "unknown", + }, + ]; + + for (let testCase of sourceHostTestCases) { + let { host, installSourceFromHost } = testCase; + + equal( + AddonManager.getInstallSourceFromHost(host), + installSourceFromHost, + `Got the expected result from getInstallFromHost for host ${host}` + ); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js new file mode 100644 index 0000000000..51b5735ae8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js @@ -0,0 +1,457 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { GMPTestUtils } = ChromeUtils.importESModule( + "resource://gre/modules/addons/GMPProvider.sys.mjs" +); +const { GMPInstallManager } = ChromeUtils.importESModule( + "resource://gre/modules/GMPInstallManager.sys.mjs" +); +const { GMPPrefs, GMP_PLUGIN_IDS, OPEN_H264_ID, WIDEVINE_ID } = + ChromeUtils.importESModule("resource://gre/modules/GMPUtils.sys.mjs"); +const { UpdateUtils } = ChromeUtils.importESModule( + "resource://gre/modules/UpdateUtils.sys.mjs" +); + +XPCOMUtils.defineLazyGetter( + this, + "pluginsBundle", + () => new Localization(["toolkit/about/aboutPlugins.ftl"]) +); + +var gMockAddons = new Map(); +var gMockEmeAddons = new Map(); + +const mockH264Addon = Object.freeze({ + id: OPEN_H264_ID, + isValid: true, + isInstalled: false, + nameId: "plugins-openh264-name", + descriptionId: "plugins-openh264-description", +}); +gMockAddons.set(mockH264Addon.id, mockH264Addon); + +const mockWidevineAddon = Object.freeze({ + id: WIDEVINE_ID, + isValid: true, + isInstalled: false, + nameId: "plugins-widevine-name", + descriptionId: "plugins-widevine-description", +}); +gMockAddons.set(mockWidevineAddon.id, mockWidevineAddon); +gMockEmeAddons.set(mockWidevineAddon.id, mockWidevineAddon); + +var gInstalledAddonId = ""; +var gPrefs = Services.prefs; +var gGetKey = GMPPrefs.getPrefKey; + +const MockGMPInstallManagerPrototype = { + checkForAddons: () => + Promise.resolve({ + usedFallback: true, + addons: [...gMockAddons.values()], + }), + + installAddon: addon => { + gInstalledAddonId = addon.id; + return Promise.resolve(); + }, +}; + +add_setup(async () => { + Assert.deepEqual( + GMP_PLUGIN_IDS, + Array.from(gMockAddons.keys()), + "set of mock addons matches the actual set of plugins" + ); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + // The GMPProvider does not register until the first content process + // is launched, so we simulate that by firing this notification. + Services.obs.notifyObservers(null, "ipc:first-content-process-created"); + + await promiseStartupManager(); + + gPrefs.setBoolPref(GMPPrefs.KEY_LOGGING_DUMP, true); + gPrefs.setIntPref(GMPPrefs.KEY_LOGGING_LEVEL, 0); + gPrefs.setBoolPref(GMPPrefs.KEY_EME_ENABLED, true); + for (let addon of gMockAddons.values()) { + gPrefs.setBoolPref(gGetKey(GMPPrefs.KEY_PLUGIN_VISIBLE, addon.id), true); + gPrefs.setBoolPref( + gGetKey(GMPPrefs.KEY_PLUGIN_FORCE_SUPPORTED, addon.id), + true + ); + } +}); + +add_task(async function test_notInstalled() { + for (let addon of gMockAddons.values()) { + gPrefs.setCharPref(gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id), ""); + gPrefs.setBoolPref(gGetKey(GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), false); + } + + let addons = await promiseAddonsByIDs([...gMockAddons.keys()]); + Assert.equal(addons.length, gMockAddons.size); + + for (let addon of addons) { + Assert.ok(!addon.isInstalled); + Assert.equal(addon.type, "plugin"); + Assert.equal(addon.version, ""); + + let mockAddon = gMockAddons.get(addon.id); + + Assert.notEqual(mockAddon, null); + let name = await pluginsBundle.formatValue(mockAddon.nameId); + Assert.equal(addon.name, name); + let description = await pluginsBundle.formatValue(mockAddon.descriptionId); + Assert.equal(addon.description, description); + + Assert.ok(!addon.isActive); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.userDisabled); + + Assert.equal( + addon.blocklistState, + Ci.nsIBlocklistService.STATE_NOT_BLOCKED + ); + Assert.equal(addon.scope, AddonManager.SCOPE_APPLICATION); + Assert.equal(addon.pendingOperations, AddonManager.PENDING_NONE); + Assert.equal(addon.operationsRequiringRestart, AddonManager.PENDING_NONE); + + Assert.equal( + addon.permissions, + AddonManager.PERM_CAN_UPGRADE | AddonManager.PERM_CAN_ENABLE + ); + + Assert.equal(addon.updateDate, null); + + Assert.ok(addon.isCompatible); + Assert.ok(addon.isPlatformCompatible); + Assert.ok(addon.providesUpdatesSecurely); + Assert.ok(!addon.foreignInstall); + + let libraries = addon.pluginLibraries; + Assert.ok(libraries); + Assert.equal(libraries.length, 0); + Assert.equal(addon.pluginFullpath, ""); + } +}); + +add_task(async function test_installed() { + const TEST_DATE = new Date(2013, 0, 1, 12); + const TEST_VERSION = "1.2.3.4"; + const TEST_TIME_SEC = Math.round(TEST_DATE.getTime() / 1000); + + let addons = await promiseAddonsByIDs([...gMockAddons.keys()]); + Assert.equal(addons.length, gMockAddons.size); + + for (let addon of addons) { + let mockAddon = gMockAddons.get(addon.id); + Assert.notEqual(mockAddon, null); + + let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append(addon.id); + file.append(TEST_VERSION); + gPrefs.setBoolPref( + gGetKey(GMPPrefs.KEY_PLUGIN_ENABLED, mockAddon.id), + false + ); + gPrefs.setIntPref( + gGetKey(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, mockAddon.id), + TEST_TIME_SEC + ); + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, mockAddon.id), + TEST_VERSION + ); + + Assert.ok(addon.isInstalled); + Assert.equal(addon.type, "plugin"); + Assert.ok(!addon.isActive); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.userDisabled); + + let name = await pluginsBundle.formatValue(mockAddon.nameId); + Assert.equal(addon.name, name); + Assert.equal(addon.version, TEST_VERSION); + + Assert.equal( + addon.permissions, + AddonManager.PERM_CAN_UPGRADE | AddonManager.PERM_CAN_ENABLE + ); + + Assert.equal(addon.updateDate.getTime(), TEST_TIME_SEC * 1000); + + let libraries = addon.pluginLibraries; + Assert.ok(libraries); + Assert.equal(libraries.length, 1); + Assert.equal(libraries[0], TEST_VERSION); + let fullpath = addon.pluginFullpath; + Assert.equal(fullpath.length, 1); + Assert.equal(fullpath[0], file.path); + } +}); + +add_task(async function test_enable() { + let addons = await promiseAddonsByIDs([...gMockAddons.keys()]); + Assert.equal(addons.length, gMockAddons.size); + + for (let addon of addons) { + gPrefs.setBoolPref(gGetKey(GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true); + + Assert.ok(addon.isActive); + Assert.ok(!addon.appDisabled); + Assert.ok(!addon.userDisabled); + + Assert.equal( + addon.permissions, + AddonManager.PERM_CAN_UPGRADE | AddonManager.PERM_CAN_DISABLE + ); + } +}); + +add_task(async function test_globalEmeDisabled() { + let addons = await promiseAddonsByIDs([...gMockEmeAddons.keys()]); + Assert.equal(addons.length, gMockEmeAddons.size); + + gPrefs.setBoolPref(GMPPrefs.KEY_EME_ENABLED, false); + for (let addon of addons) { + Assert.ok(!addon.isActive); + Assert.ok(addon.appDisabled); + Assert.ok(!addon.userDisabled); + + Assert.equal(addon.permissions, 0); + } + gPrefs.setBoolPref(GMPPrefs.KEY_EME_ENABLED, true); +}); + +add_task(async function test_autoUpdatePrefPersistance() { + let addons = await promiseAddonsByIDs([...gMockAddons.keys()]); + Assert.equal(addons.length, gMockAddons.size); + + for (let addon of addons) { + let autoupdateKey = gGetKey(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, addon.id); + gPrefs.clearUserPref(autoupdateKey); + + addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + Assert.ok(!gPrefs.getBoolPref(autoupdateKey)); + + addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; + Assert.equal(addon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE); + Assert.ok(gPrefs.getBoolPref(autoupdateKey)); + + addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + Assert.ok(!gPrefs.prefHasUserValue(autoupdateKey)); + } +}); + +function createMockPluginFilesIfNeeded(aFile, aPluginId) { + function createFile(aFileName) { + let f = aFile.clone(); + f.append(aFileName); + if (!f.exists()) { + f.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + } + } + + let id = aPluginId.substring(4); + let libName = AppConstants.DLL_PREFIX + id + AppConstants.DLL_SUFFIX; + + createFile(libName); + if (aPluginId == WIDEVINE_ID) { + createFile("manifest.json"); + } else { + createFile(id + ".info"); + } +} + +add_task(async function test_pluginRegistration() { + const TEST_VERSION = "1.2.3.4"; + + let addedPaths = []; + let removedPaths = []; + let clearPaths = () => { + addedPaths = []; + removedPaths = []; + }; + + const MockGMPService = { + addPluginDirectory: path => { + if (!addedPaths.includes(path)) { + addedPaths.push(path); + } + }, + removePluginDirectory: path => { + if (!removedPaths.includes(path)) { + removedPaths.push(path); + } + }, + removeAndDeletePluginDirectory: path => { + if (!removedPaths.includes(path)) { + removedPaths.push(path); + } + }, + }; + + let profD = do_get_profile(); + for (let addon of gMockAddons.values()) { + await GMPTestUtils.overrideGmpService(MockGMPService, () => + testAddon(addon) + ); + } + + async function testAddon(addon) { + let file = profD.clone(); + file.append(addon.id); + file.append(TEST_VERSION); + + gPrefs.setBoolPref(gGetKey(GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true); + + // Test that plugin registration fails if the plugin dynamic library and + // info files are not present. + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id), + TEST_VERSION + ); + clearPaths(); + await promiseRestartManager(); + Assert.equal(addedPaths.indexOf(file.path), -1); + Assert.deepEqual(removedPaths, [file.path]); + + // Create dummy GMP library/info files, and test that plugin registration + // succeeds during startup, now that we've added GMP info/lib files. + createMockPluginFilesIfNeeded(file, addon.id); + + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id), + TEST_VERSION + ); + clearPaths(); + await promiseRestartManager(); + Assert.notEqual(addedPaths.indexOf(file.path), -1); + Assert.deepEqual(removedPaths, []); + + // Setting the ABI to something invalid should cause plugin to be removed at startup. + clearPaths(); + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_ABI, addon.id), + "invalid-ABI" + ); + await promiseRestartManager(); + Assert.equal(addedPaths.indexOf(file.path), -1); + Assert.deepEqual(removedPaths, [file.path]); + + // Setting the ABI to expected ABI should cause registration at startup. + clearPaths(); + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id), + TEST_VERSION + ); + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_ABI, addon.id), + UpdateUtils.ABI + ); + await promiseRestartManager(); + Assert.notEqual(addedPaths.indexOf(file.path), -1); + Assert.deepEqual(removedPaths, []); + + // Check that clearing the version doesn't trigger registration. + clearPaths(); + gPrefs.clearUserPref(gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id)); + Assert.deepEqual(addedPaths, []); + Assert.deepEqual(removedPaths, [file.path]); + + // Restarting with no version set should not trigger registration. + clearPaths(); + await promiseRestartManager(); + Assert.equal(addedPaths.indexOf(file.path), -1); + Assert.equal(removedPaths.indexOf(file.path), -1); + + // Changing the pref mid-session should cause unregistration and registration. + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id), + TEST_VERSION + ); + clearPaths(); + const TEST_VERSION_2 = "5.6.7.8"; + let file2 = Services.dirsvc.get("ProfD", Ci.nsIFile); + file2.append(addon.id); + file2.append(TEST_VERSION_2); + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id), + TEST_VERSION_2 + ); + Assert.deepEqual(addedPaths, [file2.path]); + Assert.deepEqual(removedPaths, [file.path]); + + // Disabling the plugin should cause unregistration. + gPrefs.setCharPref( + gGetKey(GMPPrefs.KEY_PLUGIN_VERSION, addon.id), + TEST_VERSION + ); + clearPaths(); + gPrefs.setBoolPref(gGetKey(GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), false); + Assert.deepEqual(addedPaths, []); + Assert.deepEqual(removedPaths, [file.path]); + + // Restarting with the plugin disabled should not cause registration. + clearPaths(); + await promiseRestartManager(); + Assert.equal(addedPaths.indexOf(file.path), -1); + Assert.equal(removedPaths.indexOf(file.path), -1); + + // Re-enabling the plugin should cause registration. + clearPaths(); + gPrefs.setBoolPref(gGetKey(GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true); + Assert.deepEqual(addedPaths, [file.path]); + Assert.deepEqual(removedPaths, []); + } +}); + +add_task(async function test_periodicUpdate() { + // The GMPInstallManager constructor has an empty body, + // so replacing the prototype is safe. + let originalInstallManager = GMPInstallManager.prototype; + GMPInstallManager.prototype = MockGMPInstallManagerPrototype; + + let addons = await promiseAddonsByIDs([...gMockAddons.keys()]); + Assert.equal(addons.length, gMockAddons.size); + + for (let addon of addons) { + gPrefs.clearUserPref(gGetKey(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, addon.id)); + + addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + gPrefs.setIntPref(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0); + let result = await addon.findUpdates( + {}, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + Assert.strictEqual(result, false); + + addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; + gPrefs.setIntPref(GMPPrefs.KEY_UPDATE_LAST_CHECK, Date.now() / 1000 - 60); + result = await addon.findUpdates( + {}, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + Assert.strictEqual(result, false); + + const SEC_IN_A_DAY = 24 * 60 * 60; + gPrefs.setIntPref( + GMPPrefs.KEY_UPDATE_LAST_CHECK, + Date.now() / 1000 - 2 * SEC_IN_A_DAY + ); + gInstalledAddonId = ""; + result = await addon.findUpdates( + {}, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + Assert.strictEqual(result, true); + Assert.equal(gInstalledAddonId, addon.id); + } + + GMPInstallManager.prototype = originalInstallManager; +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_harness.js b/toolkit/mozapps/extensions/test/xpcshell/test_harness.js new file mode 100644 index 0000000000..8be3cdcf22 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_harness.js @@ -0,0 +1,13 @@ +"use strict"; + +// Test that the test harness is sane. + +// Test that the temporary directory is actually overridden in the +// directory service. +add_task(async function test_TmpD_override() { + equal( + FileUtils.getDir("TmpD", []).path, + AddonTestUtils.tempDir.path, + "Should get the correct temporary directory from the directory service" + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_hidden.js b/toolkit/mozapps/extensions/test/xpcshell/test_hidden.js new file mode 100644 index 0000000000..3d1c187b81 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hidden.js @@ -0,0 +1,251 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +AddonTestUtils.init(this); +AddonTestUtils.usePrivilegedSignatures = id => id.startsWith("privileged"); + +add_task(async function setup() { + await ExtensionTestUtils.startAddonManager(); +}); + +add_task(async function test_hidden() { + let xpi1 = createTempWebExtensionFile({ + manifest: { + browser_specific_settings: { + gecko: { + id: "privileged@tests.mozilla.org", + }, + }, + + name: "Hidden Extension", + hidden: true, + }, + }); + + let xpi2 = createTempWebExtensionFile({ + manifest: { + browser_specific_settings: { + gecko: { + id: "unprivileged@tests.mozilla.org", + }, + }, + + name: "Non-Hidden Extension", + hidden: true, + }, + }); + + await promiseInstallAllFiles([xpi1, xpi2]); + + let [addon1, addon2] = await promiseAddonsByIDs([ + "privileged@tests.mozilla.org", + "unprivileged@tests.mozilla.org", + ]); + + ok(addon1.isPrivileged, "Privileged is privileged"); + ok(addon1.hidden, "Privileged extension should be hidden"); + ok(!addon2.isPrivileged, "Unprivileged extension is not privileged"); + ok(!addon2.hidden, "Unprivileged extension should not be hidden"); + + await promiseRestartManager(); + + [addon1, addon2] = await promiseAddonsByIDs([ + "privileged@tests.mozilla.org", + "unprivileged@tests.mozilla.org", + ]); + + ok(addon1.isPrivileged, "Privileged is privileged"); + ok(addon1.hidden, "Privileged extension should be hidden"); + ok(!addon2.isPrivileged, "Unprivileged extension is not privileged"); + ok(!addon2.hidden, "Unprivileged extension should not be hidden"); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", + manifest: { + browser_specific_settings: { gecko: { id: "privileged@but-temporary" } }, + hidden: true, + }, + }); + await extension.startup(); + let tempAddon = extension.addon; + ok(tempAddon.isPrivileged, "Temporary add-on is privileged"); + ok( + !tempAddon.hidden, + "Temporary add-on is not hidden despite being privileged" + ); + await extension.unload(); +}); + +add_task( + { + pref_set: [["extensions.manifestV3.enabled", true]], + }, + async function test_hidden_and_browser_action_props_are_mutually_exclusive() { + const TEST_CASES = [ + { + title: "hidden and browser_action", + manifest: { + hidden: true, + browser_action: {}, + }, + expectError: true, + }, + { + title: "hidden and page_action", + manifest: { + hidden: true, + page_action: {}, + }, + expectError: true, + }, + { + title: "hidden, browser_action and page_action", + manifest: { + hidden: true, + browser_action: {}, + page_action: {}, + }, + expectError: true, + }, + { + title: "hidden and no browser_action or page_action", + manifest: { + hidden: true, + }, + expectError: false, + }, + { + title: "not hidden and browser_action", + manifest: { + hidden: false, + browser_action: {}, + }, + expectError: false, + }, + { + title: "not hidden and page_action", + manifest: { + hidden: false, + page_action: {}, + }, + expectError: false, + }, + { + title: "no hidden prop and browser_action", + manifest: { + browser_action: {}, + }, + expectError: false, + }, + { + title: "hidden and action", + manifest: { + manifest_version: 3, + hidden: true, + action: {}, + }, + expectError: true, + }, + { + title: "hidden, action and page_action", + manifest: { + manifest_version: 3, + hidden: true, + action: {}, + page_action: {}, + }, + expectError: true, + }, + { + title: "no hidden prop and action", + manifest: { + manifest_version: 3, + action: {}, + }, + expectError: false, + }, + { + title: "no hidden prop and page_action", + manifest: { + page_action: {}, + }, + expectError: false, + }, + { + title: "hidden and action but not privileged", + manifest: { + manifest_version: 3, + hidden: true, + action: {}, + }, + expectError: false, + isPrivileged: false, + }, + { + title: "hidden and browser_action but not privileged", + manifest: { + hidden: true, + browser_action: {}, + }, + expectError: false, + isPrivileged: false, + }, + { + title: "hidden and page_action but not privileged", + manifest: { + hidden: true, + page_action: {}, + }, + expectError: false, + isPrivileged: false, + }, + ]; + + let count = 0; + + for (const { + title, + manifest, + expectError, + isPrivileged = true, + } of TEST_CASES) { + info(`== ${title} ==`); + + // Thunderbird doesn't have page actions. + if (manifest.page_action && AppConstants.MOZ_APP_NAME == "thunderbird") { + continue; + } + + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_specific_settings: { + gecko: { + id: `${isPrivileged ? "" : "not-"}privileged@ext-${count++}`, + }, + }, + permissions: ["mozillaAddons"], + ...manifest, + }, + background() { + /* globals browser */ + browser.test.sendMessage("ok"); + }, + isPrivileged, + }); + + if (expectError) { + await Assert.rejects( + extension.startup(), + /Cannot use browser and\/or page actions in hidden add-ons/, + "expected extension not started" + ); + } else { + await extension.startup(); + await extension.awaitMessage("ok"); + await extension.unload(); + } + } + } +); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_install.js new file mode 100644 index 0000000000..2d2ee3df24 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js @@ -0,0 +1,1050 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var testserver = createHttpServer({ hosts: ["example.com"] }); +var gInstallDate; + +const ADDONS = { + test_install1: { + manifest: { + name: "Test 1", + version: "1.0", + browser_specific_settings: { gecko: { id: "addon1@tests.mozilla.org" } }, + }, + }, + test_install2_1: { + manifest: { + name: "Test 2", + version: "2.0", + browser_specific_settings: { gecko: { id: "addon2@tests.mozilla.org" } }, + }, + }, + test_install2_2: { + manifest: { + name: "Test 2", + version: "3.0", + browser_specific_settings: { gecko: { id: "addon2@tests.mozilla.org" } }, + }, + }, + test_install3: { + manifest: { + name: "Test 3", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon3@tests.mozilla.org", + strict_min_version: "0", + strict_max_version: "0", + update_url: "http://example.com/update.json", + }, + }, + }, + }, +}; + +const XPIS = {}; + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +const UPDATE_JSON = { + addons: { + "addon3@tests.mozilla.org": { + updates: [ + { + version: "1.0", + applications: { + gecko: { + strict_min_version: "0", + strict_max_version: "2", + }, + }, + }, + ], + }, + }, +}; + +const GETADDONS_JSON = { + page_size: 25, + page_count: 1, + count: 1, + next: null, + previous: null, + results: [ + { + name: "Test 2", + type: "extension", + guid: "addon2@tests.mozilla.org", + current_version: { + version: "1.0", + files: [ + { + size: 2, + url: "http://example.com/test_install2_1.xpi", + }, + ], + }, + authors: [ + { + name: "Test Creator", + url: "http://example.com/creator.html", + }, + ], + summary: "Repository summary", + description: "Repository description", + }, + ], +}; + +function checkInstall(install, expected) { + for (let [key, value] of Object.entries(expected)) { + if (value instanceof Ci.nsIURI) { + equal( + install[key] && install[key].spec, + value.spec, + `Expected value of install.${key}` + ); + } else { + deepEqual(install[key], value, `Expected value of install.${key}`); + } + } +} + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + for (let [name, data] of Object.entries(ADDONS)) { + XPIS[name] = AddonTestUtils.createTempWebExtensionFile(data); + testserver.registerFile(`/addons/${name}.xpi`, XPIS[name]); + } + + await promiseStartupManager(); + + // Create and configure the HTTP server. + AddonTestUtils.registerJSON(testserver, "/update.json", UPDATE_JSON); + testserver.registerDirectory("/data/", do_get_file("data")); + testserver.registerPathHandler("/redirect", function (aRequest, aResponse) { + aResponse.setStatusLine(null, 301, "Moved Permanently"); + let url = aRequest.host + ":" + aRequest.port + aRequest.queryString; + aResponse.setHeader("Location", "http://" + url); + }); + gPort = testserver.identity.primaryPort; +}); + +// Checks that an install from a local file proceeds as expected +add_task(async function test_install_file() { + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + AddonManager.getInstallForFile(XPIS.test_install1), + ]); + + let uri = Services.io.newFileURI(XPIS.test_install1); + checkInstall(install, { + type: "extension", + version: "1.0", + name: "Test 1", + state: AddonManager.STATE_DOWNLOADED, + sourceURI: uri, + }); + + let { addon } = install; + checkAddon("addon1@tests.mozilla.org", addon, { + install, + sourceURI: uri, + }); + notEqual(addon.syncGUID, null); + equal( + addon.getResourceURI("manifest.json").spec, + `jar:${uri.spec}!/manifest.json` + ); + + let activeInstalls = await AddonManager.getAllInstalls(); + equal(activeInstalls.length, 1); + equal(activeInstalls[0], install); + + let fooInstalls = await AddonManager.getInstallsByTypes(["foo"]); + equal(fooInstalls.length, 0); + + let extensionInstalls = await AddonManager.getInstallsByTypes(["extension"]); + equal(extensionInstalls.length, 1); + equal(extensionInstalls[0], install); + + await expectEvents( + { + addonEvents: { + "addon1@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + }, + () => install.install() + ); + + addon = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + ok(addon); + + ok(!hasFlag(addon.permissions, AddonManager.PERM_CAN_ENABLE)); + ok(hasFlag(addon.permissions, AddonManager.PERM_CAN_DISABLE)); + + let updateDate = Date.now(); + + await promiseRestartManager(); + + activeInstalls = await AddonManager.getAllInstalls(); + equal(activeInstalls, 0); + + let a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + let uri2 = do_get_addon_root_uri(profileDir, "addon1@tests.mozilla.org"); + + checkAddon("addon1@tests.mozilla.org", a1, { + type: "extension", + version: "1.0", + name: "Test 1", + foreignInstall: false, + sourceURI: Services.io.newFileURI(XPIS.test_install1), + }); + + notEqual(a1.syncGUID, null); + ok(a1.syncGUID.length >= 9); + + ok(isExtensionInBootstrappedList(profileDir, a1.id)); + ok(XPIS.test_install1.exists()); + do_check_in_crash_annotation(a1.id, a1.version); + + let difference = a1.installDate.getTime() - updateDate; + if (Math.abs(difference) > MAX_TIME_DIFFERENCE) { + do_throw("Add-on install time was out by " + difference + "ms"); + } + + difference = a1.updateDate.getTime() - updateDate; + if (Math.abs(difference) > MAX_TIME_DIFFERENCE) { + do_throw("Add-on update time was out by " + difference + "ms"); + } + + equal(a1.getResourceURI("manifest.json").spec, uri2 + "manifest.json"); + + // Ensure that extension bundle (or icon if unpacked) has updated + // lastModifiedDate. + let testFile = getAddonFile(a1); + ok(testFile.exists()); + difference = testFile.lastModifiedTime - Date.now(); + ok(Math.abs(difference) < MAX_TIME_DIFFERENCE); + + await a1.uninstall(); + let { id, version } = a1; + await promiseRestartManager(); + do_check_not_in_crash_annotation(id, version); +}); + +// Tests that an install from a url downloads. +add_task(async function test_install_url() { + let url = "http://example.com/addons/test_install2_1.xpi"; + let install = await AddonManager.getInstallForURL(url, { + name: "Test 2", + version: "1.0", + }); + checkInstall(install, { + version: "1.0", + name: "Test 2", + state: AddonManager.STATE_AVAILABLE, + sourceURI: Services.io.newURI(url), + }); + + let activeInstalls = await AddonManager.getAllInstalls(); + equal(activeInstalls.length, 1); + equal(activeInstalls[0], install); + + await expectEvents( + { + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded", returnValue: false }, + ], + }, + () => { + install.install(); + } + ); + + checkInstall(install, { + version: "2.0", + name: "Test 2", + state: AddonManager.STATE_DOWNLOADED, + }); + equal(install.addon.install, install); + + await expectEvents( + { + addonEvents: { + "addon2@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => install.install() + ); + + let updateDate = Date.now(); + + await promiseRestartManager(); + + let installs = await AddonManager.getAllInstalls(); + equal(installs, 0); + + let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + checkAddon("addon2@tests.mozilla.org", a2, { + type: "extension", + version: "2.0", + name: "Test 2", + sourceURI: Services.io.newURI(url), + }); + notEqual(a2.syncGUID, null); + + ok(isExtensionInBootstrappedList(profileDir, a2.id)); + ok(XPIS.test_install2_1.exists()); + do_check_in_crash_annotation(a2.id, a2.version); + + let difference = a2.installDate.getTime() - updateDate; + Assert.lessOrEqual( + Math.abs(difference), + MAX_TIME_DIFFERENCE, + "Add-on install time was correct" + ); + + difference = a2.updateDate.getTime() - updateDate; + Assert.lessOrEqual( + Math.abs(difference), + MAX_TIME_DIFFERENCE, + "Add-on update time was correct" + ); + + gInstallDate = a2.installDate; +}); + +// Tests that installing a new version of an existing add-on works +add_task(async function test_install_new_version() { + let url = "http://example.com/addons/test_install2_2.xpi"; + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + AddonManager.getInstallForURL(url, { + name: "Test 2", + version: "3.0", + }), + ]); + + checkInstall(install, { + version: "3.0", + name: "Test 2", + state: AddonManager.STATE_AVAILABLE, + existingAddon: null, + }); + + let activeInstalls = await AddonManager.getAllInstalls(); + equal(activeInstalls.length, 1); + equal(activeInstalls[0], install); + + await expectEvents( + { + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded", returnValue: false }, + ], + }, + () => { + install.install(); + } + ); + + checkInstall(install, { + version: "3.0", + name: "Test 2", + state: AddonManager.STATE_DOWNLOADED, + existingAddon: await AddonManager.getAddonByID("addon2@tests.mozilla.org"), + }); + + equal(install.addon.install, install); + + // Installation will continue when there is nothing returned. + await expectEvents( + { + addonEvents: { + "addon2@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => install.install() + ); + + await promiseRestartManager(); + + let installs2 = await AddonManager.getInstallsByTypes(null); + equal(installs2.length, 0); + + let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + checkAddon("addon2@tests.mozilla.org", a2, { + type: "extension", + version: "3.0", + name: "Test 2", + isActive: true, + foreignInstall: false, + sourceURI: Services.io.newURI(url), + installDate: gInstallDate, + }); + + ok(isExtensionInBootstrappedList(profileDir, a2.id)); + ok(XPIS.test_install2_2.exists()); + do_check_in_crash_annotation(a2.id, a2.version); + + // Update date should be later (or the same if this test is too fast) + ok(a2.installDate <= a2.updateDate); + + await a2.uninstall(); +}); + +// Tests that an install that requires a compatibility update works +add_task(async function test_install_compat_update() { + let url = "http://example.com/addons/test_install3.xpi"; + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + await AddonManager.getInstallForURL(url, { + name: "Test 3", + version: "1.0", + }), + ]); + + checkInstall(install, { + version: "1.0", + name: "Test 3", + state: AddonManager.STATE_AVAILABLE, + }); + + let activeInstalls = await AddonManager.getInstallsByTypes(null); + equal(activeInstalls.length, 1); + equal(activeInstalls[0], install); + + await expectEvents( + { + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded", returnValue: false }, + ], + }, + () => { + install.install(); + } + ); + + checkInstall(install, { + version: "1.0", + name: "Test 3", + state: AddonManager.STATE_DOWNLOADED, + existingAddon: null, + }); + checkAddon("addon3@tests.mozilla.org", install.addon, { + appDisabled: false, + }); + + // Continue the install + await expectEvents( + { + addonEvents: { + "addon3@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => install.install() + ); + + await promiseRestartManager(); + + let installs = await AddonManager.getAllInstalls(); + equal(installs, 0); + + let a3 = await AddonManager.getAddonByID("addon3@tests.mozilla.org"); + checkAddon("addon3@tests.mozilla.org", a3, { + type: "extension", + version: "1.0", + name: "Test 3", + isActive: true, + appDisabled: false, + }); + notEqual(a3.syncGUID, null); + + ok(isExtensionInBootstrappedList(profileDir, a3.id)); + + ok(XPIS.test_install3.exists()); + await a3.uninstall(); +}); + +add_task(async function test_compat_update_local() { + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + AddonManager.getInstallForFile(XPIS.test_install3), + ]); + ok(install.addon.isCompatible); + + await expectEvents( + { + addonEvents: { + "addon3@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => install.install() + ); + + await promiseRestartManager(); + + let a3 = await AddonManager.getAddonByID("addon3@tests.mozilla.org"); + checkAddon("addon3@tests.mozilla.org", a3, { + type: "extension", + version: "1.0", + name: "Test 3", + isActive: true, + appDisabled: false, + }); + notEqual(a3.syncGUID, null); + + ok(isExtensionInBootstrappedList(profileDir, a3.id)); + + ok(XPIS.test_install3.exists()); + await a3.uninstall(); +}); + +// Test that after cancelling a download it is removed from the active installs +add_task(async function test_cancel() { + let url = "http://example.com/addons/test_install3.xpi"; + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + AddonManager.getInstallForURL(url, { + name: "Test 3", + version: "1.0", + }), + ]); + + checkInstall(install, { + version: "1.0", + name: "Test 3", + state: AddonManager.STATE_AVAILABLE, + }); + + let activeInstalls = await AddonManager.getInstallsByTypes(null); + equal(activeInstalls.length, 1); + equal(activeInstalls[0], install); + + let promise; + function cancel() { + promise = expectEvents( + { + installEvents: [{ event: "onDownloadCancelled" }], + }, + () => { + install.cancel(); + } + ); + } + + await expectEvents( + { + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded", callback: cancel }, + ], + }, + () => { + install.install(); + } + ); + + await promise; + + let file = install.file; + + // Allow the file removal to complete + activeInstalls = await AddonManager.getAllInstalls(); + equal(activeInstalls.length, 0); + ok(!file.exists()); +}); + +// Check that cancelling the install from onDownloadStarted actually cancels it +add_task(async function test_cancel_onDownloadStarted() { + let url = "http://example.com/addons/test_install2_1.xpi"; + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + AddonManager.getInstallForURL(url), + ]); + + equal(install.file, null); + + install.addListener({ + onDownloadStarted() { + install.removeListener(this); + executeSoon(() => install.cancel()); + }, + }); + + let promise = AddonTestUtils.promiseInstallEvent("onDownloadCancelled"); + install.install(); + await promise; + + // Wait another tick to see if it continues downloading. + // The listener only really tests if we give it time to see progress, the + // file check isn't ideal either + install.addListener({ + onDownloadProgress() { + do_throw("Download should not have continued"); + }, + onDownloadEnded() { + do_throw("Download should not have continued"); + }, + }); + + let file = install.file; + await Promise.resolve(); + ok(!file.exists()); +}); + +// Checks that cancelling the install from onDownloadEnded actually cancels it +add_task(async function test_cancel_onDownloadEnded() { + let url = "http://example.com/addons/test_install2_1.xpi"; + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + AddonManager.getInstallForURL(url), + ]); + + equal(install.file, null); + + let promise; + function cancel() { + promise = expectEvents( + { + installEvents: [{ event: "onDownloadCancelled" }], + }, + async () => { + install.cancel(); + } + ); + } + + await expectEvents( + { + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded", callback: cancel }, + ], + }, + () => { + install.install(); + } + ); + + await promise; + + install.addListener({ + onInstallStarted() { + do_throw("Install should not have continued"); + }, + }); +}); + +// Verify that the userDisabled value carries over to the upgrade by default +add_task(async function test_userDisabled_update() { + let url = "http://example.com/addons/test_install2_1.xpi"; + let [, install] = await Promise.all([ + AddonTestUtils.promiseInstallEvent("onNewInstall"), + AddonManager.getInstallForURL(url), + ]); + + await install.install(); + + ok(!install.addon.userDisabled); + await install.addon.disable(); + + let addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + checkAddon("addon2@tests.mozilla.org", addon, { + userDisabled: true, + isActive: false, + }); + + url = "http://example.com/addons/test_install2_2.xpi"; + install = await AddonManager.getInstallForURL(url); + await install.install(); + + checkAddon("addon2@tests.mozilla.org", install.addon, { + userDisabled: true, + isActive: false, + }); + + await promiseRestartManager(); + + addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + checkAddon("addon2@tests.mozilla.org", addon, { + userDisabled: true, + isActive: false, + }); + + await addon.uninstall(); +}); + +// Verify that changing the userDisabled value before onInstallEnded works +add_task(async function test_userDisabled() { + let url = "http://example.com/addons/test_install2_1.xpi"; + let install = await AddonManager.getInstallForURL(url); + await install.install(); + + ok(!install.addon.userDisabled); + + let addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + checkAddon("addon2@tests.mozilla.org", addon, { + userDisabled: false, + isActive: true, + }); + + url = "http://example.com/addons/test_install2_2.xpi"; + install = await AddonManager.getInstallForURL(url); + + install.addListener({ + onInstallStarted() { + ok(!install.addon.userDisabled); + install.addon.disable(); + }, + }); + + await install.install(); + + addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + checkAddon("addon2@tests.mozilla.org", addon, { + userDisabled: true, + isActive: false, + }); + + await addon.uninstall(); +}); + +// Checks that metadata is not stored if the pref is set to false +add_task(async function test_18_1() { + AddonTestUtils.registerJSON(testserver, "/getaddons.json", GETADDONS_JSON); + Services.prefs.setCharPref( + PREF_GETADDONS_BYIDS, + "http://example.com/getaddons.json" + ); + + Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true); + Services.prefs.setBoolPref( + "extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", + false + ); + + let url = "http://example.com/addons/test_install2_1.xpi"; + let install = await AddonManager.getInstallForURL(url); + await install.install(); + + notEqual(install.addon.fullDescription, "Repository description"); + + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + notEqual(addon.fullDescription, "Repository description"); + + await addon.uninstall(); +}); + +// Checks that metadata is downloaded for new installs and is visible before and +// after restart +add_task(async function test_metadata() { + Services.prefs.setBoolPref( + "extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", + true + ); + + let url = "http://example.com/addons/test_install2_1.xpi"; + let install = await AddonManager.getInstallForURL(url); + await install.install(); + + equal(install.addon.fullDescription, "Repository description"); + + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + equal(addon.fullDescription, "Repository description"); + + await addon.uninstall(); +}); + +// Do the same again to make sure it works when the data is already in the cache +add_task(async function test_metadata_again() { + let url = "http://example.com/addons/test_install2_1.xpi"; + let install = await AddonManager.getInstallForURL(url); + await install.install(); + + equal(install.addon.fullDescription, "Repository description"); + + await promiseRestartManager(); + + let addon = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + equal(addon.fullDescription, "Repository description"); + + await addon.uninstall(); +}); + +// Tests that an install can be restarted after being cancelled +add_task(async function test_restart() { + let url = "http://example.com/addons/test_install1.xpi"; + let install = await AddonManager.getInstallForURL(url); + equal(install.state, AddonManager.STATE_AVAILABLE); + + install.addListener({ + onDownloadEnded() { + install.removeListener(this); + install.cancel(); + }, + }); + + try { + await install.install(); + ok(false, "Install should not have succeeded"); + } catch (err) {} + + let promise = expectEvents( + { + addonEvents: { + "addon1@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded" }, + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => { + install.install(); + } + ); + + await Promise.all([ + promise, + promiseWebExtensionStartup("addon1@tests.mozilla.org"), + ]); + + await install.addon.uninstall(); +}); + +// Tests that an install can be restarted after being cancelled when a hash +// was provided +add_task(async function test_restart_hash() { + let url = "http://example.com/addons/test_install1.xpi"; + let install = await AddonManager.getInstallForURL(url, { + hash: do_get_file_hash(XPIS.test_install1), + }); + equal(install.state, AddonManager.STATE_AVAILABLE); + + install.addListener({ + onDownloadEnded() { + install.removeListener(this); + install.cancel(); + }, + }); + + try { + await install.install(); + ok(false, "Install should not have succeeded"); + } catch (err) {} + + let promise = expectEvents( + { + addonEvents: { + "addon1@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded" }, + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => { + install.install(); + } + ); + + await Promise.all([ + promise, + promiseWebExtensionStartup("addon1@tests.mozilla.org"), + ]); + + await install.addon.uninstall(); +}); + +// Tests that an install with a bad hash can be restarted after it fails, though +// it will only fail again +add_task(async function test_restart_badhash() { + let url = "http://example.com/addons/test_install1.xpi"; + let install = await AddonManager.getInstallForURL(url, { hash: "sha1:foo" }); + equal(install.state, AddonManager.STATE_AVAILABLE); + + install.addListener({ + onDownloadEnded() { + install.removeListener(this); + install.cancel(); + }, + }); + + try { + await install.install(); + ok(false, "Install should not have succeeded"); + } catch (err) {} + + try { + await install.install(); + ok(false, "Install should not have succeeded"); + } catch (err) { + ok(true, "Resumed install should have failed"); + } +}); + +// Tests that installs with a hash for a local file work +add_task(async function test_local_hash() { + let url = Services.io.newFileURI(XPIS.test_install1).spec; + let install = await AddonManager.getInstallForURL(url, { + hash: do_get_file_hash(XPIS.test_install1), + }); + + checkInstall(install, { + state: AddonManager.STATE_DOWNLOADED, + error: 0, + }); + + install.cancel(); +}); + +// Test that an install cannot be canceled after the install is completed. +add_task(async function test_cancel_completed() { + let url = "http://example.com/addons/test_install1.xpi"; + let install = await AddonManager.getInstallForURL(url); + + let cancelPromise = new Promise((resolve, reject) => { + install.addListener({ + onInstallEnded() { + try { + install.cancel(); + reject("Cancel should fail."); + } catch (e) { + resolve(); + } + }, + }); + }); + + install.install(); + await cancelPromise; + + equal(install.state, AddonManager.STATE_INSTALLED); +}); + +// Test that an install may be canceled after a redirect. +add_task(async function test_cancel_redirect() { + let url = "http://example.com/redirect?/addons/test_install1.xpi"; + let install = await AddonManager.getInstallForURL(url); + + install.addListener({ + onDownloadProgress() { + install.cancel(); + }, + }); + + let promise = AddonTestUtils.promiseInstallEvent("onDownloadCancelled"); + + install.install(); + await promise; + + equal(install.state, AddonManager.STATE_CANCELLED); +}); + +// Tests that an install can be restarted during onDownloadCancelled after being +// cancelled in mid-download +add_task(async function test_restart2() { + let url = "http://example.com/addons/test_install1.xpi"; + let install = await AddonManager.getInstallForURL(url); + + equal(install.state, AddonManager.STATE_AVAILABLE); + + install.addListener({ + onDownloadProgress() { + install.removeListener(this); + install.cancel(); + }, + }); + + let promise = AddonTestUtils.promiseInstallEvent("onDownloadCancelled"); + install.install(); + await promise; + + equal(install.state, AddonManager.STATE_CANCELLED); + + promise = expectEvents( + { + addonEvents: { + "addon1@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded" }, + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => { + let file = install.file; + install.install(); + notEqual(file.path, install.file.path); + ok(!file.exists()); + } + ); + + await Promise.all([ + promise, + promiseWebExtensionStartup("addon1@tests.mozilla.org"), + ]); + + await install.addon.uninstall(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_installOrigins.js b/toolkit/mozapps/extensions/test/xpcshell/test_installOrigins.js new file mode 100644 index 0000000000..49f3b336c7 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_installOrigins.js @@ -0,0 +1,535 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.usePrivilegedSignatures = false; +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "42", + "42" +); + +Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); +// This pref is not set in Thunderbird, and needs to be true for the test to pass. +Services.prefs.setBoolPref("extensions.postDownloadThirdPartyPrompt", true); + +let server = AddonTestUtils.createHttpServer({ + hosts: ["example.com", "example.org", "amo.example.com", "github.io"], +}); + +server.registerFile( + `/addons/origins.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 2, + name: "Install Origins test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "origins@example.com", + }, + }, + install_origins: ["http://example.com"], + }, + }) +); + +server.registerFile( + `/addons/sitepermission.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 2, + name: "Install Origins sitepermission test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "sitepermission@example.com", + }, + }, + install_origins: ["http://example.com"], + site_permissions: ["midi"], + }, + }) +); + +server.registerFile( + `/addons/sitepermission-suffix.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 2, + name: "Install Origins sitepermission public suffix test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "sitepermission-suffix@github.io", + }, + }, + install_origins: ["http://github.io"], + site_permissions: ["midi"], + }, + }) +); + +server.registerFile( + `/addons/two_origins.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 2, + name: "Install Origins test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "two_origins@example.com", + }, + }, + install_origins: ["http://example.com", "http://example.org"], + }, + }) +); + +server.registerFile( + `/addons/no_origins.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 2, + name: "Install Origins test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "no_origins@example.com", + }, + }, + }, + }) +); + +server.registerFile( + `/addons/empty_origins.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 2, + name: "Install Origins test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "no_origins@example.com", + }, + }, + install_origins: [], + }, + }) +); + +server.registerFile( + `/addons/v3_origins.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 3, + name: "Install Origins test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "v3_origins@example.com", + }, + }, + install_origins: ["http://example.com"], + }, + }) +); + +server.registerFile( + `/addons/v3_no_origins.xpi`, + AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 3, + name: "Install Origins test", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "v3_no_origins@example.com", + }, + }, + }, + }) +); + +function testInstallEvent(expectTelemetry) { + const snapshot = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS, + true + ); + + ok( + snapshot.parent && !!snapshot.parent.length, + "Got parent telemetry events in the snapshot" + ); + + let events = snapshot.parent + .filter( + ([timestamp, category, method, object, value, extra]) => + category === "addonsManager" && + method == "install" && + extra.step == expectTelemetry.step + ) + .map(event => event[5]); + equal(events.length, 1, "one event for install completion"); + Assert.deepEqual(events[0], expectTelemetry, "telemetry matches"); +} + +function promiseCompleteWebInstall( + install, + triggeringPrincipal, + expectPrompts = true +) { + let listener; + return new Promise(_resolve => { + let resolve = () => { + install.removeListener(listener); + _resolve(); + }; + + listener = { + onDownloadFailed: resolve, + onDownloadCancelled: resolve, + onInstallFailed: resolve, + onInstallCancelled: resolve, + onInstallEnded: resolve, + onInstallPostponed: resolve, + }; + + install.addListener(listener); + + // Observers to bypass panels and continue install. + if (expectPrompts) { + TestUtils.topicObserved("addon-install-blocked").then(([subject]) => { + let installInfo = subject.wrappedJSObject; + info(`==== test got addon-install-blocked ${subject} ${installInfo}`); + installInfo.install(); + }); + + TestUtils.topicObserved("addon-install-confirmation").then( + (subject, data) => { + info(`==== test got addon-install-confirmation`); + let installInfo = subject.wrappedJSObject; + for (let installer of installInfo.installs) { + installer.install(); + } + } + ); + TestUtils.topicObserved("webextension-permission-prompt").then( + ([subject]) => { + const { info } = subject.wrappedJSObject || {}; + info.resolve(); + } + ); + } + + AddonManager.installAddonFromWebpage( + "application/x-xpinstall", + null /* aBrowser */, + triggeringPrincipal, + install + ); + }); +} + +async function testAddonInstall(test) { + let { name, xpiUrl, installPrincipal, expectState, expectTelemetry } = test; + info(`testAddonInstall: ${name}`); + let expectInstall = expectState == AddonManager.STATE_INSTALLED; + let install = await AddonManager.getInstallForURL(xpiUrl, { + triggeringPrincipal: installPrincipal, + }); + await promiseCompleteWebInstall(install, installPrincipal, expectInstall); + + // Test origins telemetry + testInstallEvent(expectTelemetry); + + if (expectInstall) { + equal( + install.state, + expectState, + `${name} ${install.addon.id} install was completed` + ); + // Wait the extension startup to ensure manifest.json has been read, + // otherwise we get NS_ERROR_FILE_NOT_FOUND log spam. + await WebExtensionPolicy.getByID(install.addon.id)?.readyPromise; + await install.addon.uninstall(); + } else { + equal( + install.state, + expectState, + `${name} ${install.addon?.id} install failed` + ); + } +} + +let ssm = Services.scriptSecurityManager; +const PRINCIPAL_AMO = ssm.createContentPrincipalFromOrigin( + "https://amo.example.com" +); +const PRINCIPAL_COM = + ssm.createContentPrincipalFromOrigin("http://example.com"); +const SUB_PRINCIPAL_COM = ssm.createContentPrincipalFromOrigin( + "http://abc.example.com" +); +const THIRDPARTY_PRINCIPAL_COM = ssm.createContentPrincipalFromOrigin( + "http://fake-example.com" +); +const PRINCIPAL_ORG = + ssm.createContentPrincipalFromOrigin("http://example.org"); +const PRINCIPAL_ETLD = ssm.createContentPrincipalFromOrigin("http://github.io"); + +const TESTS = [ + { + name: "Install MV2 with install_origins", + xpiUrl: "http://example.com/addons/origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "origins@example.com", + install_origins: "1", + }, + }, + { + name: "Install MV2 without install_origins", + xpiUrl: "http://example.com/addons/no_origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "no_origins@example.com", + install_origins: "0", + }, + }, + { + name: "Install valid xpi location from invalid website", + xpiUrl: "http://example.com/addons/origins.xpi", + installPrincipal: PRINCIPAL_ORG, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "origins@example.com", + error: "ERROR_INVALID_DOMAIN", + install_origins: "1", + }, + }, + { + name: "Install invalid xpi location from valid website", + xpiUrl: "http://example.org/addons/origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "origins@example.com", + error: "ERROR_INVALID_DOMAIN", + install_origins: "1", + }, + }, + { + name: "Install MV3 with install_origins", + xpiUrl: "http://example.com/addons/v3_origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "v3_origins@example.com", + install_origins: "1", + }, + }, + { + name: "Install MV3 with install_origins from AMO", + xpiUrl: "http://example.com/addons/v3_origins.xpi", + installPrincipal: PRINCIPAL_AMO, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "v3_origins@example.com", + install_origins: "1", + }, + }, + { + name: "Install MV3 without install_origins", + xpiUrl: "http://example.com/addons/v3_no_origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "v3_no_origins@example.com", + error: "ERROR_INVALID_DOMAIN", + install_origins: "0", + }, + }, + { + // An installing principal with install permission is + // considered "AMO" in code, and will always be allowed. + name: "Install MV3 without install_origins from AMO", + xpiUrl: "http://example.com/addons/v3_no_origins.xpi", + installPrincipal: PRINCIPAL_AMO, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "v3_no_origins@example.com", + install_origins: "0", + }, + }, + { + name: "Install MV3 without install_origins from null principal", + xpiUrl: "http://example.com/addons/v3_no_origins.xpi", + installPrincipal: Services.scriptSecurityManager.createNullPrincipal({}), + expectState: AddonManager.STATE_CANCELLED, + expectTelemetry: { step: "site_blocked", install_origins: "0" }, + }, + { + name: "Install addon with two install_origins", + xpiUrl: "http://example.com/addons/two_origins.xpi", + installPrincipal: PRINCIPAL_ORG, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "two_origins@example.com", + install_origins: "1", + }, + }, + { + name: "Install addon with two install_origins", + xpiUrl: "http://example.com/addons/two_origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "two_origins@example.com", + install_origins: "1", + }, + }, + { + name: "Install from site with empty install_origins", + xpiUrl: "http://example.com/addons/empty_origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "no_origins@example.com", + error: "ERROR_INVALID_DOMAIN", + install_origins: "1", + }, + }, + { + name: "Install from site with empty install_origins", + xpiUrl: "http://example.com/addons/empty_origins.xpi", + installPrincipal: PRINCIPAL_ORG, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "no_origins@example.com", + error: "ERROR_INVALID_DOMAIN", + install_origins: "1", + }, + }, + { + name: "Install with empty install_origins from AMO", + xpiUrl: "http://amo.example.com/addons/empty_origins.xpi", + installPrincipal: PRINCIPAL_AMO, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "no_origins@example.com", + install_origins: "1", + }, + }, + { + name: "Install sitepermission from domain", + xpiUrl: "http://example.com/addons/sitepermission.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "sitepermission@example.com", + install_origins: "1", + }, + }, + { + name: "Install sitepermission from subdomain", + xpiUrl: "http://example.com/addons/sitepermission.xpi", + installPrincipal: SUB_PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "sitepermission@example.com", + install_origins: "1", + }, + }, + { + name: "Install sitepermission from thirdparty domain should fail", + xpiUrl: "http://example.com/addons/sitepermission.xpi", + installPrincipal: THIRDPARTY_PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "sitepermission@example.com", + error: "ERROR_INVALID_DOMAIN", + install_origins: "1", + }, + }, + { + name: "Install sitepermission from different domain", + xpiUrl: "http://example.com/addons/sitepermission.xpi", + installPrincipal: PRINCIPAL_ORG, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "sitepermission@example.com", + error: "ERROR_INVALID_DOMAIN", + install_origins: "1", + }, + }, + { + name: "Install sitepermission from public suffix domain", + xpiUrl: "http://github.io/addons/sitepermission-suffix.xpi", + installPrincipal: PRINCIPAL_ETLD, + expectState: AddonManager.STATE_INSTALL_FAILED, + expectTelemetry: { + step: "failed", + addon_id: "sitepermission-suffix@github.io", + error: "ERROR_INVALID_DOMAIN", + install_origins: "1", + }, + }, +]; + +add_task(async function test_install_url() { + Services.prefs.setBoolPref("extensions.install_origins.enabled", true); + PermissionTestUtils.add( + PRINCIPAL_AMO, + "install", + Services.perms.ALLOW_ACTION + ); + await promiseStartupManager(); + + for (let test of TESTS) { + await testAddonInstall(test); + } +}); + +add_task(async function test_install_origins_disabled() { + Services.prefs.setBoolPref("extensions.install_origins.enabled", false); + await testAddonInstall({ + name: "Install MV3 without install_origins, verification disabled", + xpiUrl: "http://example.com/addons/v3_no_origins.xpi", + installPrincipal: PRINCIPAL_COM, + expectState: AddonManager.STATE_INSTALLED, + expectTelemetry: { + step: "completed", + addon_id: "v3_no_origins@example.com", + }, + }); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_cancel.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_cancel.js new file mode 100644 index 0000000000..0be6ec0359 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_cancel.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +var testserver = createHttpServer({ hosts: ["example.com"] }); + +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1.9.2" +); + +class TestListener { + constructor(listener) { + this.listener = listener; + } + + onDataAvailable(...args) { + this.origListener.onDataAvailable(...args); + } + + onStartRequest(request) { + this.origListener.onStartRequest(request); + } + + onStopRequest(request, status) { + if (this.listener.onStopRequest) { + this.listener.onStopRequest(request, status); + } + this.origListener.onStopRequest(request, status); + } +} + +function startListener(listener) { + let observer = { + observe(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (channel.URI.spec === "http://example.com/addons/test.xpi") { + let channelListener = new TestListener(listener); + channelListener.origListener = subject + .QueryInterface(Ci.nsITraceableChannel) + .setNewListener(channelListener); + Services.obs.removeObserver(observer, "http-on-modify-request"); + } + }, + }; + Services.obs.addObserver(observer, "http-on-modify-request"); +} + +add_task(async function setup() { + let xpi = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + name: "Test", + version: "1.0", + browser_specific_settings: { gecko: { id: "cancel@test" } }, + }, + }); + testserver.registerFile(`/addons/test.xpi`, xpi); + await AddonTestUtils.promiseStartupManager(); +}); + +// This test checks that canceling an install after the download is completed fails +// and throws an exception as expected +add_task(async function test_install_cancelled() { + let url = "http://example.com/addons/test.xpi"; + let install = await AddonManager.getInstallForURL(url, { + name: "Test", + version: "1.0", + }); + + let cancelInstall = new Promise(resolve => { + startListener({ + onStopRequest() { + resolve(Promise.resolve().then(() => install.cancel())); + }, + }); + }); + + await install.install().then(() => { + ok(true, "install succeeded"); + }); + + await cancelInstall + .then(() => { + ok(false, "cancel should not succeed"); + }) + .catch(e => { + ok(!!e, "cancel threw an exception"); + }); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_file_change.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_file_change.js new file mode 100644 index 0000000000..75cc91038e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_file_change.js @@ -0,0 +1,180 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); + +/* globals browser */ + +add_task(async function setup() { + await ExtensionTestUtils.startAddonManager(); +}); + +async function createXPIWithID(addonId, version = "1.0") { + let xpiFile = await createTempWebExtensionFile({ + manifest: { + version, + browser_specific_settings: { gecko: { id: addonId } }, + }, + }); + return xpiFile; +} + +const ERROR_PATTERN_INSTALL_FAIL = /Failed to install .+ from .+ to /; +const ERROR_PATTERN_POSTPONE_FAIL = /Failed to postpone install of /; + +async function promiseInstallFail(install, expectedErrorPattern) { + let { messages } = await promiseConsoleOutput(async () => { + await Assert.rejects( + install.install(), + /^Error: Install failed: onInstallFailed$/ + ); + }); + messages = messages.filter(msg => expectedErrorPattern.test(msg.message)); + equal(messages.length, 1, "Expected log messages"); + equal(install.state, AddonManager.STATE_INSTALL_FAILED); + equal(install.error, AddonManager.ERROR_FILE_ACCESS); + equal((await AddonManager.getAllInstalls()).length, 0, "no pending installs"); +} + +add_task(async function test_file_deleted() { + let xpiFile = await createXPIWithID("delete@me"); + let install = await AddonManager.getInstallForFile(xpiFile); + equal(install.state, AddonManager.STATE_DOWNLOADED); + + xpiFile.remove(false); + + await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL); + + equal(await AddonManager.getAddonByID("delete@me"), null); +}); + +add_task(async function test_file_emptied() { + let xpiFile = await createXPIWithID("empty@me"); + let install = await AddonManager.getInstallForFile(xpiFile); + equal(install.state, AddonManager.STATE_DOWNLOADED); + + await IOUtils.write(xpiFile.path, new Uint8Array()); + + await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL); + + equal(await AddonManager.getAddonByID("empty@me"), null); +}); + +add_task(async function test_file_replaced() { + let xpiFile = await createXPIWithID("replace@me"); + let install = await AddonManager.getInstallForFile(xpiFile); + equal(install.state, AddonManager.STATE_DOWNLOADED); + + await IOUtils.copy( + ( + await createXPIWithID("replace@me", "2") + ).path, + xpiFile.path + ); + + await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL); + + equal(await AddonManager.getAddonByID("replace@me"), null); +}); + +async function do_test_update_with_file_replaced(wantPostponeTest) { + const ADDON_ID = wantPostponeTest ? "postpone@me" : "update@me"; + function backgroundWithPostpone() { + // The registration of this listener postpones the update. + browser.runtime.onUpdateAvailable.addListener(() => { + browser.test.fail("Unusable update should not call onUpdateAvailable"); + }); + } + await promiseInstallWebExtension({ + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { + id: ADDON_ID, + update_url: `http://example.com/update-${ADDON_ID}.json`, + }, + }, + }, + background: wantPostponeTest ? backgroundWithPostpone : () => {}, + }); + + server.registerFile( + `/update-${ADDON_ID}.xpi`, + await createTempWebExtensionFile({ + manifest: { + version: "2.0", + browser_specific_settings: { gecko: { id: ADDON_ID } }, + }, + }) + ); + AddonTestUtils.registerJSON(server, `/update-${ADDON_ID}.json`, { + addons: { + [ADDON_ID]: { + updates: [ + { + version: "2.0", + update_link: `http://example.com/update-${ADDON_ID}.xpi`, + }, + ], + }, + }, + }); + + // Setup completed, let's try to verify that file corruption halts the update. + + let addon = await promiseAddonByID(ADDON_ID); + equal(addon.version, "1.0"); + + let update = await promiseFindAddonUpdates( + addon, + AddonManager.UPDATE_WHEN_USER_REQUESTED + ); + let install = update.updateAvailable; + equal(install.version, "2.0"); + equal(install.state, AddonManager.STATE_AVAILABLE); + equal(install.existingAddon, addon); + equal(install.file, null); + + let promptCount = 0; + let didReplaceFile = false; + install.promptHandler = async function () { + ++promptCount; + equal(install.state, AddonManager.STATE_DOWNLOADED); + await IOUtils.copy( + ( + await createXPIWithID(ADDON_ID, "3") + ).path, + install.file.path + ); + didReplaceFile = true; + equal(install.state, AddonManager.STATE_DOWNLOADED, "State not changed"); + }; + + if (wantPostponeTest) { + await promiseInstallFail(install, ERROR_PATTERN_POSTPONE_FAIL); + } else { + await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL); + } + + equal(promptCount, 1); + ok(didReplaceFile, "Replaced update with different file"); + + // Now verify that the add-on is still at the old version. + addon = await promiseAddonByID(ADDON_ID); + equal(addon.version, "1.0"); + + await addon.uninstall(); +} + +add_task(async function test_update_and_file_replaced() { + await do_test_update_with_file_replaced(); +}); + +add_task(async function test_update_postponed_and_file_replaced() { + await do_test_update_with_file_replaced(/* wantPostponeTest = */ true); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js b/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js new file mode 100644 index 0000000000..af88b55959 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// use httpserver to find an available port +var gServer = new HttpServer(); +gServer.start(-1); +gPort = gServer.identity.primaryPort; + +var addon_url = "http://localhost:" + gPort + "/test.xpi"; +var icon32_url = "http://localhost:" + gPort + "/icon.png"; +var icon64_url = "http://localhost:" + gPort + "/icon64.png"; + +async function run_test() { + do_test_pending(); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + await promiseStartupManager(); + + test_1(); +} + +async function test_1() { + let aInstall = await AddonManager.getInstallForURL(addon_url); + Assert.equal(aInstall.iconURL, null); + Assert.notEqual(aInstall.icons, null); + Assert.equal(aInstall.icons[32], undefined); + Assert.equal(aInstall.icons[64], undefined); + test_2(); +} + +async function test_2() { + let aInstall = await AddonManager.getInstallForURL(addon_url, { + icons: icon32_url, + }); + Assert.equal(aInstall.iconURL, icon32_url); + Assert.notEqual(aInstall.icons, null); + Assert.equal(aInstall.icons[32], icon32_url); + Assert.equal(aInstall.icons[64], undefined); + test_3(); +} + +async function test_3() { + let aInstall = await AddonManager.getInstallForURL(addon_url, { + icons: { 32: icon32_url }, + }); + Assert.equal(aInstall.iconURL, icon32_url); + Assert.notEqual(aInstall.icons, null); + Assert.equal(aInstall.icons[32], icon32_url); + Assert.equal(aInstall.icons[64], undefined); + test_4(); +} + +async function test_4() { + let aInstall = await AddonManager.getInstallForURL(addon_url, { + icons: { 32: icon32_url, 64: icon64_url }, + }); + Assert.equal(aInstall.iconURL, icon32_url); + Assert.notEqual(aInstall.icons, null); + Assert.equal(aInstall.icons[32], icon32_url); + Assert.equal(aInstall.icons[64], icon64_url); + executeSoon(do_test_finished); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_installtrigger_deprecation.js b/toolkit/mozapps/extensions/test/xpcshell/test_installtrigger_deprecation.js new file mode 100644 index 0000000000..dfaeaa44f2 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_installtrigger_deprecation.js @@ -0,0 +1,346 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42"); + +const testserver = createHttpServer({ hosts: ["example.com"] }); + +function createTestPage(body) { + return ` + + + + + + ${body} + + + `; +} + +testserver.registerPathHandler( + "/installtrigger_ua_detection.html", + (request, response) => { + response.write( + createTestPage(` +