From 536562284f1ffe459f432f5810d59dcb6eadb8cc Mon Sep 17 00:00:00 2001 From: trav90 Date: Sat, 12 May 2018 08:19:07 -0500 Subject: Make safebrowsing optional at build time - Part 3: toolkit/ --- toolkit/components/build/nsToolkitCompsModule.cpp | 10 ++++++++++ toolkit/components/downloads/moz.build | 8 ++++++-- toolkit/components/jsdownloads/src/DownloadIntegration.jsm | 7 +++++++ toolkit/components/moz.build | 4 +++- toolkit/modules/AppConstants.jsm | 7 +++++++ 5 files changed, 33 insertions(+), 3 deletions(-) (limited to 'toolkit') diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index 22bb434a0..f81e35f23 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -26,11 +26,13 @@ #include "nsTypeAheadFind.h" +#ifdef MOZ_URL_CLASSIFIER #include "ApplicationReputation.h" #include "nsUrlClassifierDBService.h" #include "nsUrlClassifierStreamUpdater.h" #include "nsUrlClassifierUtils.h" #include "nsUrlClassifierPrefixSet.h" +#endif #include "nsBrowserStatusFilter.h" #include "mozilla/FinalizationWitnessService.h" @@ -91,6 +93,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadProxy) NS_GENERIC_FACTORY_CONSTRUCTOR(nsTypeAheadFind) +#ifdef MOZ_URL_CLASSIFIER NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ApplicationReputationService, ApplicationReputationService::GetSingleton) NS_GENERIC_FACTORY_CONSTRUCTOR(nsUrlClassifierPrefixSet) @@ -115,6 +118,7 @@ nsUrlClassifierDBServiceConstructor(nsISupports *aOuter, REFNSIID aIID, return rv; } +#endif NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter) #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) @@ -148,11 +152,13 @@ NS_DEFINE_NAMED_CID(NS_DOWNLOADPLATFORM_CID); NS_DEFINE_NAMED_CID(NS_DOWNLOAD_CID); NS_DEFINE_NAMED_CID(NS_FIND_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_TYPEAHEADFIND_CID); +#ifdef MOZ_URL_CLASSIFIER NS_DEFINE_NAMED_CID(NS_APPLICATION_REPUTATION_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERPREFIXSET_CID); NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERDBSERVICE_CID); NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTREAMUPDATER_CID); NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID); +#endif NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID); #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID); @@ -184,11 +190,13 @@ static const Module::CIDEntry kToolkitCIDs[] = { { &kNS_DOWNLOAD_CID, false, nullptr, nsDownloadProxyConstructor }, { &kNS_FIND_SERVICE_CID, false, nullptr, nsFindServiceConstructor }, { &kNS_TYPEAHEADFIND_CID, false, nullptr, nsTypeAheadFindConstructor }, +#ifdef MOZ_URL_CLASSIFIER { &kNS_APPLICATION_REPUTATION_SERVICE_CID, false, nullptr, ApplicationReputationServiceConstructor }, { &kNS_URLCLASSIFIERPREFIXSET_CID, false, nullptr, nsUrlClassifierPrefixSetConstructor }, { &kNS_URLCLASSIFIERDBSERVICE_CID, false, nullptr, nsUrlClassifierDBServiceConstructor }, { &kNS_URLCLASSIFIERSTREAMUPDATER_CID, false, nullptr, nsUrlClassifierStreamUpdaterConstructor }, { &kNS_URLCLASSIFIERUTILS_CID, false, nullptr, nsUrlClassifierUtilsConstructor }, +#endif { &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor }, #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor }, @@ -221,12 +229,14 @@ static const Module::ContractIDEntry kToolkitContracts[] = { { NS_DOWNLOADPLATFORM_CONTRACTID, &kNS_DOWNLOADPLATFORM_CID }, { NS_FIND_SERVICE_CONTRACTID, &kNS_FIND_SERVICE_CID }, { NS_TYPEAHEADFIND_CONTRACTID, &kNS_TYPEAHEADFIND_CID }, +#ifdef MOZ_URL_CLASSIFIER { NS_APPLICATION_REPUTATION_SERVICE_CONTRACTID, &kNS_APPLICATION_REPUTATION_SERVICE_CID }, { NS_URLCLASSIFIERPREFIXSET_CONTRACTID, &kNS_URLCLASSIFIERPREFIXSET_CID }, { NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID }, { NS_URICLASSIFIERSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID }, { NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID, &kNS_URLCLASSIFIERSTREAMUPDATER_CID }, { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID }, +#endif { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID }, #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID }, diff --git a/toolkit/components/downloads/moz.build b/toolkit/components/downloads/moz.build index 477db0bd6..20394a70d 100644 --- a/toolkit/components/downloads/moz.build +++ b/toolkit/components/downloads/moz.build @@ -32,8 +32,6 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'downloads' UNIFIED_SOURCES += [ - 'ApplicationReputation.cpp', - 'chromium/chrome/common/safe_browsing/csd.pb.cc', 'nsDownloadManager.cpp' ] @@ -42,6 +40,12 @@ SOURCES += [ 'SQLFunctions.cpp', ] +if CONFIG['MOZ_URL_CLASSIFIER']: + UNIFIED_SOURCES += [ + 'ApplicationReputation.cpp', + 'chromium/chrome/common/safe_browsing/csd.pb.cc' + ] + if CONFIG['OS_ARCH'] == 'WINNT': # Can't build unified because we need CreateEvent which some IPC code # included in LoadContext ends up undefining. diff --git a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm index 4069bca74..c24c5d631 100644 --- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm +++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm @@ -475,6 +475,12 @@ this.DownloadIntegration = { * } */ shouldBlockForReputationCheck(aDownload) { +#ifndef MOZ_URL_CLASSIFIER + return Promise.resolve({ + shouldBlock: false, + verdict: "", + }); +#else let hash; let sigInfo; let channelRedirects; @@ -515,6 +521,7 @@ this.DownloadIntegration = { }); }); return deferred.promise; +#endif }, #ifdef XP_WIN diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index e0b412428..c34c1f18d 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -60,7 +60,6 @@ DIRS += [ 'tooltiptext', 'typeaheadfind', 'utils', - 'url-classifier', 'urlformatter', 'viewconfig', 'workerloader', @@ -93,6 +92,9 @@ if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: if CONFIG['MOZ_TOOLKIT_SEARCH']: DIRS += ['search'] +if CONFIG['MOZ_URL_CLASSIFIER']: + DIRS += ['url-classifier'] + DIRS += ['captivedetect'] if CONFIG['OS_TARGET'] != 'Android': diff --git a/toolkit/modules/AppConstants.jsm b/toolkit/modules/AppConstants.jsm index c500b21e8..c2abc939c 100644 --- a/toolkit/modules/AppConstants.jsm +++ b/toolkit/modules/AppConstants.jsm @@ -81,6 +81,13 @@ this.AppConstants = Object.freeze({ false, #endif +MOZ_SAFE_BROWSING: +#ifdef MOZ_SAFE_BROWSING + true, +#else + false, +#endif + MOZ_TELEMETRY_REPORTING: #ifdef MOZ_TELEMETRY_REPORTING true, -- cgit v1.2.3 From 1a0462b4587a127d90e36b1f50687a223be8ba0f Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Mon, 14 May 2018 14:32:47 -0400 Subject: [*AM] Make soft-blocked phrasing more generic so people will cry less when we do our job --- toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd | 6 +++--- .../locales/en-US/chrome/mozapps/extensions/extensions.properties | 4 ++-- toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd | 6 +++--- .../en-US/chrome/mozapps/webextensions/extensions.properties | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'toolkit') diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd index f393cc906..a9490db5e 100644 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd +++ b/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd @@ -4,10 +4,10 @@ - - + + - + diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties index c4e2660ee..9d976e0e3 100644 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties +++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties @@ -28,7 +28,7 @@ notification.jetsdk=This is a Jetpack/SDK extension which are not supported in % notification.blocked=%1$S has been disabled due to security or stability issues. notification.blocked.link=More Information #LOCALIZATION NOTE (notification.softblocked) %1$S is the add-on name -notification.softblocked=%1$S is known to cause security or stability issues. +notification.softblocked=%1$S is known to cause issues. notification.softblocked.link=More Information #LOCALIZATION NOTE (notification.outdated) %1$S is the add-on name notification.outdated=An important update is available for %1$S. @@ -79,7 +79,7 @@ details.notification.incompatible=%1$S is incompatible with %2$S %3$S. details.notification.blocked=%1$S has been disabled due to security or stability issues. details.notification.blocked.link=More Information #LOCALIZATION NOTE (details.notification.softblocked) %1$S is the add-on name -details.notification.softblocked=%1$S is known to cause security or stability issues. +details.notification.softblocked=%1$S is known to cause issues. details.notification.softblocked.link=More Information #LOCALIZATION NOTE (details.notification.outdated) %1$S is the add-on name details.notification.outdated=An important update is available for %1$S. diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd b/toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd index f393cc906..a9490db5e 100644 --- a/toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd @@ -4,10 +4,10 @@ - - + + - + diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties b/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties index 10c0f81c3..b51c3e12d 100644 --- a/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties @@ -31,7 +31,7 @@ notification.unsigned.link=More Information notification.blocked=%1$S has been disabled due to security or stability issues. notification.blocked.link=More Information #LOCALIZATION NOTE (notification.softblocked) %1$S is the add-on name -notification.softblocked=%1$S is known to cause security or stability issues. +notification.softblocked=%1$S is known to cause issues. notification.softblocked.link=More Information #LOCALIZATION NOTE (notification.outdated) %1$S is the add-on name notification.outdated=An important update is available for %1$S. @@ -86,7 +86,7 @@ details.notification.unsigned.link=More Information details.notification.blocked=%1$S has been disabled due to security or stability issues. details.notification.blocked.link=More Information #LOCALIZATION NOTE (details.notification.softblocked) %1$S is the add-on name -details.notification.softblocked=%1$S is known to cause security or stability issues. +details.notification.softblocked=%1$S is known to cause issues. details.notification.softblocked.link=More Information #LOCALIZATION NOTE (details.notification.outdated) %1$S is the add-on name details.notification.outdated=An important update is available for %1$S. -- cgit v1.2.3 From f86673741dce0580a73d37b6f72418877b7380d6 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Tue, 15 May 2018 05:06:42 -0400 Subject: Issue #347 - TychoAM nsBlocklistService does not support gfxBlacklist --- toolkit/components/blocklist/blocklist.manifest | 7 + toolkit/components/blocklist/moz.build | 14 + toolkit/components/blocklist/nsBlocklistService.js | 1666 ++++++++++++++++++++ .../blocklist/nsBlocklistServiceContent.js | 113 ++ toolkit/components/moz.build | 1 + toolkit/mozapps/extensions/extensions.manifest | 4 - toolkit/mozapps/extensions/moz.build | 1 - toolkit/mozapps/extensions/nsBlocklistService.js | 1478 ----------------- toolkit/mozapps/webextensions/extensions.manifest | 7 - toolkit/mozapps/webextensions/moz.build | 2 - .../mozapps/webextensions/nsBlocklistService.js | 1658 ------------------- .../webextensions/nsBlocklistServiceContent.js | 113 -- 12 files changed, 1801 insertions(+), 3263 deletions(-) create mode 100644 toolkit/components/blocklist/blocklist.manifest create mode 100644 toolkit/components/blocklist/moz.build create mode 100644 toolkit/components/blocklist/nsBlocklistService.js create mode 100644 toolkit/components/blocklist/nsBlocklistServiceContent.js delete mode 100644 toolkit/mozapps/extensions/nsBlocklistService.js delete mode 100644 toolkit/mozapps/webextensions/nsBlocklistService.js delete mode 100644 toolkit/mozapps/webextensions/nsBlocklistServiceContent.js (limited to 'toolkit') diff --git a/toolkit/components/blocklist/blocklist.manifest b/toolkit/components/blocklist/blocklist.manifest new file mode 100644 index 000000000..c770b4e7d --- /dev/null +++ b/toolkit/components/blocklist/blocklist.manifest @@ -0,0 +1,7 @@ +component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js process=main +contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e} process=main +category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1 process=main +component {e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d} nsBlocklistServiceContent.js process=content +contract @mozilla.org/extensions/blocklist;1 {e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d} process=content + +category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400 \ No newline at end of file diff --git a/toolkit/components/blocklist/moz.build b/toolkit/components/blocklist/moz.build new file mode 100644 index 000000000..3dc3be5ba --- /dev/null +++ b/toolkit/components/blocklist/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXTRA_COMPONENTS += [ + 'blocklist.manifest', + 'nsBlocklistServiceContent.js', +] + +EXTRA_PP_COMPONENTS += [ + 'nsBlocklistService.js', +] diff --git a/toolkit/components/blocklist/nsBlocklistService.js b/toolkit/components/blocklist/nsBlocklistService.js new file mode 100644 index 000000000..891346b72 --- /dev/null +++ b/toolkit/components/blocklist/nsBlocklistService.js @@ -0,0 +1,1666 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +try { + // AddonManager.jsm doesn't allow itself to be imported in the child + // process. We're used in the child process (for now), so guard against + // this. + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + /* globals AddonManagerPrivate*/ +} catch (e) { +} + +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +#ifdef MOZ_WEBEXTENSIONS +XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", + "resource://gre/modules/UpdateUtils.jsm"); +#else +XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", + "resource://gre/modules/UpdateChannel.jsm"); +#endif +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest", + "resource://gre/modules/ServiceRequest.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +const TOOLKIT_ID = "toolkit@mozilla.org"; +const KEY_PROFILEDIR = "ProfD"; +const KEY_APPDIR = "XCurProcD"; +const FILE_BLOCKLIST = "blocklist.xml"; +const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer"; +const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; +const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; +const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; +const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; +const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level"; +const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal"; +const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; +const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI"; +const PREF_ONECRL_VIA_AMO = "security.onecrl.via.amo"; +const PREF_BLOCKLIST_UPDATE_ENABLED = "services.blocklist.update_enabled"; +const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale"; +const PREF_APP_DISTRIBUTION = "distribution.id"; +const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; +const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; +const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; +const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" +const UNKNOWN_XPCOM_ABI = "unknownABI"; +const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul" +const DEFAULT_SEVERITY = 3; +const DEFAULT_LEVEL = 2; +const MAX_BLOCK_LEVEL = 3; +const SEVERITY_OUTDATED = 0; +const VULNERABILITYSTATUS_NONE = 0; +const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1; +const VULNERABILITYSTATUS_NO_UPDATE = 2; + +const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"]; + +var gLoggingEnabled = null; +var gBlocklistEnabled = true; +var gBlocklistLevel = DEFAULT_LEVEL; + +XPCOMUtils.defineLazyServiceGetter(this, "gConsole", + "@mozilla.org/consoleservice;1", + "nsIConsoleService"); + +XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker", + "@mozilla.org/xpcom/version-comparator;1", + "nsIVersionComparator"); + +XPCOMUtils.defineLazyServiceGetter(this, "gCertBlocklistService", + "@mozilla.org/security/certblocklist;1", + "nsICertBlocklist"); + +XPCOMUtils.defineLazyGetter(this, "gPref", function() { + return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService). + QueryInterface(Ci.nsIPrefBranch); +}); + +// From appinfo in Services.jsm. It is not possible to use the one in +// Services.jsm since it will not successfully QueryInterface nsIXULAppInfo in +// xpcshell tests due to other code calling Services.appinfo before the +// nsIXULAppInfo is created by the tests. +XPCOMUtils.defineLazyGetter(this, "gApp", function() { + let appinfo = Cc["@mozilla.org/xre/app-info;1"] + .getService(Ci.nsIXULRuntime); + try { + appinfo.QueryInterface(Ci.nsIXULAppInfo); + } catch (ex) { + // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). + if (!(ex instanceof Components.Exception) || + ex.result != Cr.NS_NOINTERFACE) + throw ex; + } + return appinfo; +}); + +XPCOMUtils.defineLazyGetter(this, "gABI", function() { + let abi = null; + try { + abi = gApp.XPCOMABI; + } + catch (e) { + LOG("BlockList Global gABI: XPCOM ABI unknown."); + } + +#ifdef XP_MACOSX + // Mac universal build should report a different ABI than either macppc + // or mactel. + let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. + getService(Ci.nsIMacUtils); + + if (macutils.isUniversalBinary) + abi += "-u-" + macutils.architecturesInBinary; +#endif + return abi; +}); + +XPCOMUtils.defineLazyGetter(this, "gOSVersion", function() { + let osVersion; + let sysInfo = Cc["@mozilla.org/system-info;1"]. + getService(Ci.nsIPropertyBag2); + try { + osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); + } + catch (e) { + LOG("BlockList Global gOSVersion: OS Version unknown."); + } + + if (osVersion) { + try { + osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; + } + catch (e) { + // Not all platforms have a secondary widget library, so an error is nothing to worry about. + } + osVersion = encodeURIComponent(osVersion); + } + return osVersion; +}); + +// shared code for suppressing bad cert dialogs +XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() { + let temp = { }; + Components.utils.import("resource://gre/modules/CertUtils.jsm", temp); + return temp; +}); + +/** + * Logs a string to the error console. + * @param string + * The string to write to the error console.. + */ +function LOG(string) { + if (gLoggingEnabled) { + dump("*** " + string + "\n"); + gConsole.logStringMessage(string); + } +} + +/** + * Gets a preference value, handling the case where there is no default. + * @param func + * The name of the preference function to call, on nsIPrefBranch + * @param preference + * The name of the preference + * @param defaultValue + * The default value to return in the event the preference has + * no setting + * @returns The value of the preference, or undefined if there was no + * user or default value. + */ +function getPref(func, preference, defaultValue) { + try { + return gPref[func](preference); + } + catch (e) { + } + return defaultValue; +} + +/** + * Constructs a URI to a spec. + * @param spec + * The spec to construct a URI to + * @returns The nsIURI constructed. + */ +function newURI(spec) { + var ioServ = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + return ioServ.newURI(spec, null, null); +} + +// Restarts the application checking in with observers first +function restartApp() { + // Notify all windows that an application quit has been requested. + var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. + createInstance(Ci.nsISupportsPRBool); + os.notifyObservers(cancelQuit, "quit-application-requested", null); + + // Something aborted the quit process. + if (cancelQuit.data) + return; + + var as = Cc["@mozilla.org/toolkit/app-startup;1"]. + getService(Ci.nsIAppStartup); + as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); +} + +/** + * Checks whether this blocklist element is valid for the current OS and ABI. + * If the element has an "os" attribute then the current OS must appear in + * its comma separated list for the element to be valid. Similarly for the + * xpcomabi attribute. + */ +function matchesOSABI(blocklistElement) { + if (blocklistElement.hasAttribute("os")) { + var choices = blocklistElement.getAttribute("os").split(","); + if (choices.length > 0 && choices.indexOf(gApp.OS) < 0) + return false; + } + + if (blocklistElement.hasAttribute("xpcomabi")) { + choices = blocklistElement.getAttribute("xpcomabi").split(","); + if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0) + return false; + } + + return true; +} + +/** + * Gets the current value of the locale. It's possible for this preference to + * be localized, so we have to do a little extra work here. Similar code + * exists in nsHttpHandler.cpp when building the UA string. + */ +function getLocale() { + try { + // Get the default branch + var defaultPrefs = gPref.getDefaultBranch(null); + return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE, + Ci.nsIPrefLocalizedString).data; + } catch (e) {} + + return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE); +} + +/* Get the distribution pref values, from defaults only */ +function getDistributionPrefValue(aPrefName) { + var prefValue = "default"; + + var defaults = gPref.getDefaultBranch(null); + try { + prefValue = defaults.getCharPref(aPrefName); + } catch (e) { + // use default when pref not found + } + + return prefValue; +} + +/** + * Parse a string representation of a regular expression. Needed because we + * use the /pattern/flags form (because it's detectable), which is only + * supported as a literal in JS. + * + * @param aStr + * String representation of regexp + * @return RegExp instance + */ +function parseRegExp(aStr) { + let lastSlash = aStr.lastIndexOf("/"); + let pattern = aStr.slice(1, lastSlash); + let flags = aStr.slice(lastSlash + 1); + return new RegExp(pattern, flags); +} + +/** + * Manages the Blocklist. The Blocklist is a representation of the contents of + * blocklist.xml and allows us to remotely disable / re-enable blocklisted + * items managed by the Extension Manager with an item's appDisabled property. + * It also blocklists plugins with data from blocklist.xml. + */ + +function Blocklist() { + Services.obs.addObserver(this, "xpcom-shutdown", false); + Services.obs.addObserver(this, "sessionstore-windows-restored", false); + gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); + gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); + gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), + MAX_BLOCK_LEVEL); + gPref.addObserver("extensions.blocklist.", this, false); + gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false); + this.wrappedJSObject = this; + // requests from child processes come in here, see receiveMessage. + Services.ppmm.addMessageListener("Blocklist:getPluginBlocklistState", this); + Services.ppmm.addMessageListener("Blocklist:content-blocklist-updated", this); +} + +Blocklist.prototype = { + /** + * Extension ID -> array of Version Ranges + * Each value in the version range array is a JS Object that has the + * following properties: + * "minVersion" The minimum version in a version range (default = 0) + * "maxVersion" The maximum version in a version range (default = *) + * "targetApps" Application ID -> array of Version Ranges + * (default = current application ID) + * Each value in the version range array is a JS Object that + * has the following properties: + * "minVersion" The minimum version in a version range + * (default = 0) + * "maxVersion" The maximum version in a version range + * (default = *) + */ + _addonEntries: null, + _gfxEntries: null, + _pluginEntries: null, + + shutdown: function() { + Services.obs.removeObserver(this, "xpcom-shutdown"); + Services.ppmm.removeMessageListener("Blocklist:getPluginBlocklistState", this); + Services.ppmm.removeMessageListener("Blocklist:content-blocklist-updated", this); + gPref.removeObserver("extensions.blocklist.", this); + gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this); + }, + + observe: function(aSubject, aTopic, aData) { + switch (aTopic) { + case "xpcom-shutdown": + this.shutdown(); + break; + case "nsPref:changed": + switch (aData) { + case PREF_EM_LOGGING_ENABLED: + gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); + break; + case PREF_BLOCKLIST_ENABLED: + gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); + this._loadBlocklist(); + this._blocklistUpdated(null, null); + break; + case PREF_BLOCKLIST_LEVEL: + gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), + MAX_BLOCK_LEVEL); + this._blocklistUpdated(null, null); + break; + } + break; + case "sessionstore-windows-restored": + Services.obs.removeObserver(this, "sessionstore-windows-restored"); + this._preloadBlocklist(); + break; + } + }, + + // Message manager message handlers + receiveMessage: function(aMsg) { + switch (aMsg.name) { + case "Blocklist:getPluginBlocklistState": + return this.getPluginBlocklistState(aMsg.data.addonData, + aMsg.data.appVersion, + aMsg.data.toolkitVersion); + case "Blocklist:content-blocklist-updated": + Services.obs.notifyObservers(null, "content-blocklist-updated", null); + break; + default: + throw new Error("Unknown blocklist message received from content: " + aMsg.name); + } + return undefined; + }, + + /* See nsIBlocklistService */ + isAddonBlocklisted: function(addon, appVersion, toolkitVersion) { + return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) == + Ci.nsIBlocklistService.STATE_BLOCKED; + }, + + /* See nsIBlocklistService */ + getAddonBlocklistState: function(addon, appVersion, toolkitVersion) { + if (!this._isBlocklistLoaded()) + this._loadBlocklist(); + return this._getAddonBlocklistState(addon, this._addonEntries, + appVersion, toolkitVersion); + }, + + /** + * Private version of getAddonBlocklistState that allows the caller to pass in + * the add-on blocklist entries to compare against. + * + * @param id + * The ID of the item to get the blocklist state for. + * @param version + * The version of the item to get the blocklist state for. + * @param addonEntries + * The add-on blocklist entries to compare against. + * @param appVersion + * The application version to compare to, will use the current + * version if null. + * @param toolkitVersion + * The toolkit version to compare to, will use the current version if + * null. + * @returns The blocklist state for the item, one of the STATE constants as + * defined in nsIBlocklistService. + */ + _getAddonBlocklistState: function(addon, addonEntries, appVersion, toolkitVersion) { + if (!gBlocklistEnabled) + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + + // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). + if (!appVersion && !gApp.version) + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + + if (!appVersion) + appVersion = gApp.version; + if (!toolkitVersion) + toolkitVersion = gApp.platformVersion; + + var blItem = this._findMatchingAddonEntry(addonEntries, addon); + if (!blItem) + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + + for (let currentblItem of blItem.versions) { + if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) + return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED : + Ci.nsIBlocklistService.STATE_SOFTBLOCKED; + } + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + }, + + /** + * Returns the set of prefs of the add-on stored in the blocklist file + * (probably to revert them on disabling). + * @param addon + * The add-on whose to-be-reset prefs are to be found. + */ + _getAddonPrefs: function(addon) { + let entry = this._findMatchingAddonEntry(this._addonEntries, addon); + return entry.prefs.slice(0); + }, + + _findMatchingAddonEntry: function(aAddonEntries, aAddon) { + if (!aAddon) + return null; + // Returns true if the params object passes the constraints set by entry. + // (For every non-null property in entry, the same key must exist in + // params and value must be the same) + function checkEntry(entry, params) { + for (let [key, value] of entry) { + if (value === null || value === undefined) + continue; + if (params[key]) { + if (value instanceof RegExp) { + if (!value.test(params[key])) { + return false; + } + } else if (value !== params[key]) { + return false; + } + } else { + return false; + } + } + return true; + } + + let params = {}; + for (let filter of EXTENSION_BLOCK_FILTERS) { + params[filter] = aAddon[filter]; + } + if (params.creator) + params.creator = params.creator.name; + for (let entry of aAddonEntries) { + if (checkEntry(entry.attributes, params)) { + return entry; + } + } + return null; + }, + + /* See nsIBlocklistService */ + getAddonBlocklistURL: function(addon, appVersion, toolkitVersion) { + if (!gBlocklistEnabled) + return ""; + + if (!this._isBlocklistLoaded()) + this._loadBlocklist(); + + let blItem = this._findMatchingAddonEntry(this._addonEntries, addon); + if (!blItem || !blItem.blockID) + return null; + + return this._createBlocklistURL(blItem.blockID); + }, + + _createBlocklistURL: function(id) { + let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); + url = url.replace(/%blockID%/g, id); + + return url; + }, + + notify: function(aTimer) { + if (!gBlocklistEnabled) + return; + + try { + var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); + } + catch (e) { + LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + + " is missing!"); + return; + } + + var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0); + var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1); + var daysSinceLastPing = 0; + if (pingCountVersion == 0) { + daysSinceLastPing = "new"; + } + else { + // Seconds in one day is used because nsIUpdateTimerManager stores the + // last update time in seconds. + let secondsInDay = 60 * 60 * 24; + let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0); + if (lastUpdateTime == 0) { + daysSinceLastPing = "invalid"; + } + else { + let now = Math.round(Date.now() / 1000); + daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay); + } + + if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") { + pingCountVersion = pingCountTotal = "invalid"; + } + } + + if (pingCountVersion < 1) + pingCountVersion = 1; + if (pingCountTotal < 1) + pingCountTotal = 1; + + dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); + // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). + if (gApp.version) + dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); + dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name); + // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). + if (gApp.version) + dsURI = dsURI.replace(/%VERSION%/g, gApp.version); + dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID); + dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI); + dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion); + dsURI = dsURI.replace(/%LOCALE%/g, getLocale()); +#ifdef MOZ_WEBEXTENSIONS + dsURI = dsURI.replace(/%CHANNEL%/g, UpdateUtils.UpdateChannel); +#else + dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get()); +#endif + dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion); + dsURI = dsURI.replace(/%DISTRIBUTION%/g, + getDistributionPrefValue(PREF_APP_DISTRIBUTION)); + dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g, + getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION)); + dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion); + dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal); + dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing); + dsURI = dsURI.replace(/\+/g, "%2B"); + + // Under normal operations it will take around 5,883,516 years before the + // preferences used to store pingCountVersion and pingCountTotal will rollover + // so this code doesn't bother trying to do the "right thing" here. + if (pingCountVersion != "invalid") { + pingCountVersion++; + if (pingCountVersion > 2147483647) { + // Rollover to -1 if the value is greater than what is support by an + // integer preference. The -1 indicates that the counter has been reset. + pingCountVersion = -1; + } + gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion); + } + + if (pingCountTotal != "invalid") { + pingCountTotal++; + if (pingCountTotal > 2147483647) { + // Rollover to 1 if the value is greater than what is support by an + // integer preference. + pingCountTotal = -1; + } + gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal); + } + + // Verify that the URI is valid + try { + var uri = newURI(dsURI); + } + catch (e) { + LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" + + "for: " + dsURI + ", error: " + e); + return; + } + + LOG("Blocklist::notify: Requesting " + uri.spec); + let request = new ServiceRequest(); + request.open("GET", uri.spec, true); + request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); + request.overrideMimeType("text/xml"); + request.setRequestHeader("Cache-Control", "no-cache"); + request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); + + request.addEventListener("error", event => this.onXMLError(event), false); + request.addEventListener("load", event => this.onXMLLoad(event), false); + request.send(null); + + // When the blocklist loads we need to compare it to the current copy so + // make sure we have loaded it. + if (!this._isBlocklistLoaded()) + this._loadBlocklist(); + }, + + onXMLLoad: Task.async(function*(aEvent) { + let request = aEvent.target; + try { + gCertUtils.checkCert(request.channel); + } + catch (e) { + LOG("Blocklist::onXMLLoad: " + e); + return; + } + let responseXML = request.responseXML; + if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || + (request.status != 200 && request.status != 0)) { + LOG("Blocklist::onXMLLoad: there was an error during load"); + return; + } + + var oldAddonEntries = this._addonEntries; + var oldPluginEntries = this._pluginEntries; + this._addonEntries = []; + this._gfxEntries = []; + this._pluginEntries = []; + + this._loadBlocklistFromString(request.responseText); + // We don't inform the users when the graphics blocklist changed at runtime. + // However addons and plugins blocking status is refreshed. + this._blocklistUpdated(oldAddonEntries, oldPluginEntries); + + try { + let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); + yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"}); + } catch (e) { + LOG("Blocklist::onXMLLoad: " + e); + } + }), + + onXMLError: function(aEvent) { + try { + var request = aEvent.target; + // the following may throw (e.g. a local file or timeout) + var status = request.status; + } + catch (e) { + request = aEvent.target.channel.QueryInterface(Ci.nsIRequest); + status = request.status; + } + var statusText = "nsIXMLHttpRequest channel unavailable"; + // When status is 0 we don't have a valid channel. + if (status != 0) { + try { + statusText = request.statusText; + } catch (e) { + } + } + LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + + statusText); + }, + + /** + * Finds the newest blocklist file from the application and the profile and + * load it or does nothing if neither exist. + */ + _loadBlocklist: function() { + this._addonEntries = []; + this._gfxEntries = []; + this._pluginEntries = []; + var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); + if (profFile.exists()) { + this._loadBlocklistFromFile(profFile); + return; + } + var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); + if (appFile.exists()) { + this._loadBlocklistFromFile(appFile); + return; + } + LOG("Blocklist::_loadBlocklist: no XML File found"); + }, + + /** +# The blocklist XML file looks something like this: +# +# +# +# +# +# accessibility.accesskeycausesactivation +# accessibility.blockautorefresh +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# AkHVNA== +# +# +# +# +# +# + */ + + _loadBlocklistFromFile: function(file) { + if (!gBlocklistEnabled) { + LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); + return; + } + + let telemetry = Services.telemetry; + + if (this._isBlocklistPreloaded()) { + telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false); + this._loadBlocklistFromString(this._preloadedBlocklistContent); + delete this._preloadedBlocklistContent; + return; + } + + if (!file.exists()) { + LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path); + return; + } + + telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true); + + let text = ""; + let fstream = null; + let cstream = null; + + try { + fstream = Components.classes["@mozilla.org/network/file-input-stream;1"] + .createInstance(Components.interfaces.nsIFileInputStream); + cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] + .createInstance(Components.interfaces.nsIConverterInputStream); + + fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); + cstream.init(fstream, "UTF-8", 0, 0); + + let str = {}; + let read = 0; + + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + text += str.value; + } while (read != 0); + } catch (e) { + LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e); + } finally { + if (cstream) + cstream.close(); + if (fstream) + fstream.close(); + } + + if (text) + this._loadBlocklistFromString(text); + }, + + _isBlocklistLoaded: function() { + return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null; + }, + + _isBlocklistPreloaded: function() { + return this._preloadedBlocklistContent != null; + }, + + /* Used for testing */ + _clear: function() { + this._addonEntries = null; + this._gfxEntries = null; + this._pluginEntries = null; + this._preloadedBlocklistContent = null; + }, + + _preloadBlocklist: Task.async(function*() { + let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); + try { + yield this._preloadBlocklistFile(profPath); + return; + } catch (e) { + LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) + } + + var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); + try { + yield this._preloadBlocklistFile(appFile.path); + return; + } catch (e) { + LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) + } + + LOG("Blocklist::_preloadBlocklist: no XML File found"); + }), + + _preloadBlocklistFile: Task.async(function*(path) { + if (this._addonEntries) { + // The file has been already loaded. + return; + } + + if (!gBlocklistEnabled) { + LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled"); + return; + } + + let text = yield OS.File.read(path, { encoding: "utf-8" }); + + if (!this._addonEntries) { + // Store the content only if a sync load has not been performed in the meantime. + this._preloadedBlocklistContent = text; + } + }), + + _loadBlocklistFromString : function(text) { + try { + var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. + createInstance(Ci.nsIDOMParser); + var doc = parser.parseFromString(text, "text/xml"); + if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { + LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " + + "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" + + "Received: " + doc.documentElement.namespaceURI); + return; + } + + var populateCertBlocklist = getPref("getBoolPref", PREF_ONECRL_VIA_AMO, true); + + var childNodes = doc.documentElement.childNodes; + for (let element of childNodes) { + if (!(element instanceof Ci.nsIDOMElement)) + continue; + switch (element.localName) { + case "emItems": + this._addonEntries = this._processItemNodes(element.childNodes, "emItem", + this._handleEmItemNode); + break; + case "pluginItems": + this._pluginEntries = this._processItemNodes(element.childNodes, "pluginItem", + this._handlePluginItemNode); + break; + case "certItems": + if (populateCertBlocklist) { + this._processItemNodes(element.childNodes, "certItem", + this._handleCertItemNode.bind(this)); + } + break; + case "gfxItems": + // Parse as simple list of objects. + this._gfxEntries = this._processItemNodes(element.childNodes, "gfxBlacklistEntry", + this._handleGfxBlacklistNode); + break; + default: + LOG("Blocklist::_loadBlocklistFromString: ignored entries " + element.localName); + } + } + if (populateCertBlocklist) { + gCertBlocklistService.saveEntries(); + } + if (this._gfxEntries.length > 0) { + this._notifyObserversBlocklistGFX(); + } + } + catch (e) { + LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); + return; + } + }, + + _processItemNodes: function(itemNodes, itemName, handler) { + var result = []; + for (var i = 0; i < itemNodes.length; ++i) { + var blocklistElement = itemNodes.item(i); + if (!(blocklistElement instanceof Ci.nsIDOMElement) || + blocklistElement.localName != itemName) + continue; + + handler(blocklistElement, result); + } + return result; + }, + + _handleCertItemNode: function(blocklistElement, result) { + let issuer = blocklistElement.getAttribute("issuerName"); + if (issuer) { + for (let snElement of blocklistElement.children) { + try { + gCertBlocklistService.revokeCertByIssuerAndSerial(issuer, snElement.textContent); + } catch (e) { + // we want to keep trying other elements since missing all items + // is worse than missing one + LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Issuer and Serial" + e); + } + } + return; + } + + let pubKeyHash = blocklistElement.getAttribute("pubKeyHash"); + let subject = blocklistElement.getAttribute("subject"); + + if (pubKeyHash && subject) { + try { + gCertBlocklistService.revokeCertBySubjectAndPubKey(subject, pubKeyHash); + } catch (e) { + LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Subject and PubKey" + e); + } + } + }, + + _handleEmItemNode: function(blocklistElement, result) { + if (!matchesOSABI(blocklistElement)) + return; + + let blockEntry = { + versions: [], + prefs: [], + blockID: null, + attributes: new Map() + // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes + }; + + // Any filter starting with '/' is interpreted as a regex. So if an attribute + // starts with a '/' it must be checked via a regex. + function regExpCheck(attr) { + return attr.startsWith("/") ? parseRegExp(attr) : attr; + } + + for (let filter of EXTENSION_BLOCK_FILTERS) { + let attr = blocklistElement.getAttribute(filter); + if (attr) + blockEntry.attributes.set(filter, regExpCheck(attr)); + } + + var childNodes = blocklistElement.childNodes; + + for (let x = 0; x < childNodes.length; x++) { + var childElement = childNodes.item(x); + if (!(childElement instanceof Ci.nsIDOMElement)) + continue; + if (childElement.localName === "prefs") { + let prefElements = childElement.childNodes; + for (let i = 0; i < prefElements.length; i++) { + let prefElement = prefElements.item(i); + if (!(prefElement instanceof Ci.nsIDOMElement) || + prefElement.localName !== "pref") + continue; + blockEntry.prefs.push(prefElement.textContent); + } + } + else if (childElement.localName === "versionRange") + blockEntry.versions.push(new BlocklistItemData(childElement)); + } + // if only the extension ID is specified block all versions of the + // extension for the current application. + if (blockEntry.versions.length == 0) + blockEntry.versions.push(new BlocklistItemData(null)); + + blockEntry.blockID = blocklistElement.getAttribute("blockID"); + + result.push(blockEntry); + }, + + _handlePluginItemNode: function(blocklistElement, result) { + if (!matchesOSABI(blocklistElement)) + return; + + var matchNodes = blocklistElement.childNodes; + var blockEntry = { + matches: {}, + versions: [], + blockID: null, + infoURL: null, + }; + var hasMatch = false; + for (var x = 0; x < matchNodes.length; ++x) { + var matchElement = matchNodes.item(x); + if (!(matchElement instanceof Ci.nsIDOMElement)) + continue; + if (matchElement.localName == "match") { + var name = matchElement.getAttribute("name"); + var exp = matchElement.getAttribute("exp"); + try { + blockEntry.matches[name] = new RegExp(exp, "m"); + hasMatch = true; + } catch (e) { + // Ignore invalid regular expressions + } + } + if (matchElement.localName == "versionRange") { + blockEntry.versions.push(new BlocklistItemData(matchElement)); + } + else if (matchElement.localName == "infoURL") { + blockEntry.infoURL = matchElement.textContent; + } + } + // Plugin entries require *something* to match to an actual plugin + if (!hasMatch) + return; + // Add a default versionRange if there wasn't one specified + if (blockEntry.versions.length == 0) + blockEntry.versions.push(new BlocklistItemData(null)); + + blockEntry.blockID = blocklistElement.getAttribute("blockID"); + + result.push(blockEntry); + }, + + // + // WINNT 6.0 + // 14 currently only used for Android + // + // 0x8086 + // + // 0x2582 + // 0x2782 + // + // DIRECT3D_10_LAYERS + // BLOCKED_DRIVER_VERSION + // 8.52.322.2202 + // 8.52.322.2202 + // LESS_THAN_OR_EQUAL + // foo + // foo + // foo + // foo + // + _handleGfxBlacklistNode: function (blocklistElement, result) { + const blockEntry = {}; + + // The blockID attribute is always present in the actual data produced on server + // (see https://github.com/mozilla/addons-server/blob/2016.05.05/src/olympia/blocklist/templates/blocklist/blocklist.xml#L74) + // But it is sometimes missing in test fixtures. + if (blocklistElement.hasAttribute("blockID")) { + blockEntry.blockID = blocklistElement.getAttribute("blockID"); + } + + // Trim helper (spaces, tabs, no-break spaces..) + const trim = (s) => (s || '').replace(/(^[\s\uFEFF\xA0]+)|([\s\uFEFF\xA0]+$)/g, ""); + + for (let i = 0; i < blocklistElement.childNodes.length; ++i) { + var matchElement = blocklistElement.childNodes.item(i); + if (!(matchElement instanceof Ci.nsIDOMElement)) + continue; + + let value; + if (matchElement.localName == "devices") { + value = []; + for (let j = 0; j < matchElement.childNodes.length; j++) { + const childElement = matchElement.childNodes.item(j); + const childValue = trim(childElement.textContent); + // Make sure no empty value is added. + if (childValue) { + if (/,/.test(childValue)) { + // Devices can't contain comma. + // (c.f serialization in _notifyObserversBlocklistGFX) + const e = new Error(`Unsupported device name ${childValue}`); + Components.utils.reportError(e); + } + else { + value.push(childValue); + } + } + } + } else if (matchElement.localName == "versionRange") { + value = {minVersion: trim(matchElement.getAttribute("minVersion")) || "0", + maxVersion: trim(matchElement.getAttribute("maxVersion")) || "*"}; + } else { + value = trim(matchElement.textContent); + } + if (value) { + blockEntry[matchElement.localName] = value; + } + } + result.push(blockEntry); + }, + + /* See nsIBlocklistService */ + getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) { +#ifdef MOZ_WIDGET_ANDROID + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; +#endif + if (!this._isBlocklistLoaded()) + this._loadBlocklist(); + return this._getPluginBlocklistState(plugin, this._pluginEntries, + appVersion, toolkitVersion); + }, + + /** + * Private helper to get the blocklist entry for a plugin given a set of + * blocklist entries and versions. + * + * @param plugin + * The nsIPluginTag to get the blocklist state for. + * @param pluginEntries + * The plugin blocklist entries to compare against. + * @param appVersion + * The application version to compare to, will use the current + * version if null. + * @param toolkitVersion + * The toolkit version to compare to, will use the current version if + * null. + * @returns {entry: blocklistEntry, version: blocklistEntryVersion}, + * or null if there is no matching entry. + */ + _getPluginBlocklistEntry: function(plugin, pluginEntries, appVersion, toolkitVersion) { + if (!gBlocklistEnabled) + return null; + + // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). + if (!appVersion && !gApp.version) + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + + if (!appVersion) + appVersion = gApp.version; + if (!toolkitVersion) + toolkitVersion = gApp.platformVersion; + + for (var blockEntry of pluginEntries) { + var matchFailed = false; + for (var name in blockEntry.matches) { + if (!(name in plugin) || + typeof(plugin[name]) != "string" || + !blockEntry.matches[name].test(plugin[name])) { + matchFailed = true; + break; + } + } + + if (matchFailed) + continue; + + for (let blockEntryVersion of blockEntry.versions) { + if (blockEntryVersion.includesItem(plugin.version, appVersion, + toolkitVersion)) { + return {entry: blockEntry, version: blockEntryVersion}; + } + } + } + + return null; + }, + + /** + * Private version of getPluginBlocklistState that allows the caller to pass in + * the plugin blocklist entries. + * + * @param plugin + * The nsIPluginTag to get the blocklist state for. + * @param pluginEntries + * The plugin blocklist entries to compare against. + * @param appVersion + * The application version to compare to, will use the current + * version if null. + * @param toolkitVersion + * The toolkit version to compare to, will use the current version if + * null. + * @returns The blocklist state for the item, one of the STATE constants as + * defined in nsIBlocklistService. + */ + _getPluginBlocklistState: function(plugin, pluginEntries, appVersion, toolkitVersion) { + + let r = this._getPluginBlocklistEntry(plugin, pluginEntries, + appVersion, toolkitVersion); + if (!r) { + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + } + + let {entry: blockEntry, version: blockEntryVersion} = r; + + if (blockEntryVersion.severity >= gBlocklistLevel) + return Ci.nsIBlocklistService.STATE_BLOCKED; + if (blockEntryVersion.severity == SEVERITY_OUTDATED) { + let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus; + if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE) + return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE; + if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE) + return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE; + return Ci.nsIBlocklistService.STATE_OUTDATED; + } + return Ci.nsIBlocklistService.STATE_SOFTBLOCKED; + }, + + /* See nsIBlocklistService */ + getPluginBlocklistURL: function(plugin) { + if (!this._isBlocklistLoaded()) + this._loadBlocklist(); + + let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries); + if (!r) { + return null; + } + let {entry: blockEntry, version: blockEntryVersion} = r; + if (!blockEntry.blockID) { + return null; + } + + return this._createBlocklistURL(blockEntry.blockID); + }, + + /* See nsIBlocklistService */ + getPluginInfoURL: function(plugin) { + if (!this._isBlocklistLoaded()) + this._loadBlocklist(); + + let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries); + if (!r) { + return null; + } + let {entry: blockEntry, version: blockEntryVersion} = r; + if (!blockEntry.blockID) { + return null; + } + + return blockEntry.infoURL; + }, + + _notifyObserversBlocklistGFX: function () { + // Notify `GfxInfoBase`, by passing a string serialization. + // This way we avoid spreading XML structure logics there. + const payload = this._gfxEntries.map((r) => { + return Object.keys(r).sort().filter((k) => !/id|last_modified/.test(k)).map((key) => { + let value = r[key]; + if (Array.isArray(value)) { + value = value.join(","); + } else if (value.hasOwnProperty("minVersion")) { + // When XML is parsed, both minVersion and maxVersion are set. + value = `${value.minVersion},${value.maxVersion}`; + } + return `${key}:${value}`; + }).join("\t"); + }).join("\n"); + Services.obs.notifyObservers(null, "blocklist-data-gfxItems", payload); + }, + + _notifyObserversBlocklistUpdated: function() { + Services.obs.notifyObservers(this, "blocklist-updated", ""); + Services.ppmm.broadcastAsyncMessage("Blocklist:blocklistInvalidated", {}); + }, + + _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) { + var addonList = []; + + // A helper function that reverts the prefs passed to default values. + function resetPrefs(prefs) { + for (let pref of prefs) + gPref.clearUserPref(pref); + } + const types = ["extension", "theme", "locale", "dictionary", "service"]; + AddonManager.getAddonsByTypes(types, addons => { + for (let addon of addons) { + let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED; + if (oldAddonEntries) + oldState = this._getAddonBlocklistState(addon, oldAddonEntries); + let state = this.getAddonBlocklistState(addon); + + LOG("Blocklist state for " + addon.id + " changed from " + + oldState + " to " + state); + + // We don't want to re-warn about add-ons + if (state == oldState) + continue; + + if (state === Ci.nsIBlocklistService.STATE_BLOCKED) { + // It's a hard block. We must reset certain preferences. + let prefs = this._getAddonPrefs(addon); + resetPrefs(prefs); + } + + // Ensure that softDisabled is false if the add-on is not soft blocked + if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) + addon.softDisabled = false; + + // Don't warn about add-ons becoming unblocked. + if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED) + continue; + + // If an add-on has dropped from hard to soft blocked just mark it as + // soft disabled and don't warn about it. + if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && + oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { + addon.softDisabled = true; + continue; + } + + // If the add-on is already disabled for some reason then don't warn + // about it + if (!addon.isActive) { + // But mark it as softblocked if necessary. Note that we avoid setting + // softDisabled at the same time as userDisabled to make it clear + // which was the original cause of the add-on becoming disabled in a + // way that the user can change. + if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && !addon.userDisabled) + addon.softDisabled = true; + continue; + } + + addonList.push({ + name: addon.name, + version: addon.version, + icon: addon.iconURL, + disable: false, + blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, + item: addon, + url: this.getAddonBlocklistURL(addon), + }); + } + + AddonManagerPrivate.updateAddonAppDisabledStates(); + + var phs = Cc["@mozilla.org/plugin/host;1"]. + getService(Ci.nsIPluginHost); + var plugins = phs.getPluginTags(); + + for (let plugin of plugins) { + let oldState = -1; + if (oldPluginEntries) + oldState = this._getPluginBlocklistState(plugin, oldPluginEntries); + let state = this.getPluginBlocklistState(plugin); + LOG("Blocklist state for " + plugin.name + " changed from " + + oldState + " to " + state); + // We don't want to re-warn about items + if (state == oldState) + continue; + + if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { + if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) + plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; + } + else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { + if (state != Ci.nsIBlocklistService.STATE_OUTDATED && + state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE && + state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { + addonList.push({ + name: plugin.name, + version: plugin.version, + icon: "chrome://mozapps/skin/plugins/pluginGeneric.png", + disable: false, + blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, + item: plugin, + url: this.getPluginBlocklistURL(plugin), + }); + } + } + } + + if (addonList.length == 0) { + this._notifyObserversBlocklistUpdated(); + return; + } + + if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) { + try { + let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"] + .getService(Ci.nsIBlocklistPrompt); + blockedPrompter.prompt(addonList); + } catch (e) { + LOG(e); + } + this._notifyObserversBlocklistUpdated(); + return; + } + + var args = { + restart: false, + list: addonList + }; + // This lets the dialog get the raw js object + args.wrappedJSObject = args; + + /* + Some tests run without UI, so the async code listens to a message + that can be sent programatically + */ + let applyBlocklistChanges = () => { + for (let addon of addonList) { + if (!addon.disable) + continue; + + if (addon.item instanceof Ci.nsIPluginTag) + addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED; + else { + // This add-on is softblocked. + addon.item.softDisabled = true; + // We must revert certain prefs. + let prefs = this._getAddonPrefs(addon.item); + resetPrefs(prefs); + } + } + + if (args.restart) + restartApp(); + + this._notifyObserversBlocklistUpdated(); + Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed"); + } + + Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false); + + if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) { + applyBlocklistChanges(); + return; + } + + function blocklistUnloadHandler(event) { + if (event.target.location == URI_BLOCKLIST_DIALOG) { + applyBlocklistChanges(); + blocklistWindow.removeEventListener("unload", blocklistUnloadHandler); + } + } + + let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "", + "chrome,centerscreen,dialog,titlebar", args); + if (blocklistWindow) + blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false); + }); + }, + + classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsIBlocklistService, + Ci.nsITimerCallback]), +}; + +/** + * Helper for constructing a blocklist. + */ +function BlocklistItemData(versionRangeElement) { + var versionRange = this.getBlocklistVersionRange(versionRangeElement); + this.minVersion = versionRange.minVersion; + this.maxVersion = versionRange.maxVersion; + if (versionRangeElement && versionRangeElement.hasAttribute("severity")) + this.severity = versionRangeElement.getAttribute("severity"); + else + this.severity = DEFAULT_SEVERITY; + if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) { + this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus"); + } else { + this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE; + } + this.targetApps = { }; + var found = false; + + if (versionRangeElement) { + for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { + var targetAppElement = versionRangeElement.childNodes.item(i); + if (!(targetAppElement instanceof Ci.nsIDOMElement) || + targetAppElement.localName != "targetApplication") + continue; + found = true; + // default to the current application if id is not provided. + var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID; + this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement); + } + } + // Default to all versions of the current application when no targetApplication + // elements were found + if (!found) + this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); +} + +BlocklistItemData.prototype = { + /** + * Tests if a version of an item is included in the version range and target + * application information represented by this BlocklistItemData using the + * provided application and toolkit versions. + * @param version + * The version of the item being tested. + * @param appVersion + * The application version to test with. + * @param toolkitVersion + * The toolkit version to test with. + * @returns True if the version range covers the item version and application + * or toolkit version. + */ + includesItem: function(version, appVersion, toolkitVersion) { + // Some platforms have no version for plugins, these don't match if there + // was a min/maxVersion provided + if (!version && (this.minVersion || this.maxVersion)) + return false; + + // Check if the item version matches + if (!this.matchesRange(version, this.minVersion, this.maxVersion)) + return false; + + // Check if the application version matches + if (this.matchesTargetRange(gApp.ID, appVersion)) + return true; + + // Check if the toolkit version matches + return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion); + }, + + /** + * Checks if a version is higher than or equal to the minVersion (if provided) + * and lower than or equal to the maxVersion (if provided). + * @param version + * The version to test. + * @param minVersion + * The minimum version. If null it is assumed that version is always + * larger. + * @param maxVersion + * The maximum version. If null it is assumed that version is always + * smaller. + */ + matchesRange: function(version, minVersion, maxVersion) { + if (minVersion && gVersionChecker.compare(version, minVersion) < 0) + return false; + if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0) + return false; + return true; + }, + + /** + * Tests if there is a matching range for the given target application id and + * version. + * @param appID + * The application ID to test for, may be for an application or toolkit + * @param appVersion + * The version of the application to test for. + * @returns True if this version range covers the application version given. + */ + matchesTargetRange: function(appID, appVersion) { + var blTargetApp = this.targetApps[appID]; + if (!blTargetApp) + return false; + + for (let app of blTargetApp) { + if (this.matchesRange(appVersion, app.minVersion, app.maxVersion)) + return true; + } + + return false; + }, + + /** + * Retrieves a version range (e.g. minVersion and maxVersion) for a + * blocklist item's targetApplication element. + * @param targetAppElement + * A targetApplication blocklist element. + * @returns An array of JS objects with the following properties: + * "minVersion" The minimum version in a version range (default = null). + * "maxVersion" The maximum version in a version range (default = null). + */ + getBlocklistAppVersions: function(targetAppElement) { + var appVersions = [ ]; + + if (targetAppElement) { + for (var i = 0; i < targetAppElement.childNodes.length; ++i) { + var versionRangeElement = targetAppElement.childNodes.item(i); + if (!(versionRangeElement instanceof Ci.nsIDOMElement) || + versionRangeElement.localName != "versionRange") + continue; + appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); + } + } + // return minVersion = null and maxVersion = null if no specific versionRange + // elements were found + if (appVersions.length == 0) + appVersions.push(this.getBlocklistVersionRange(null)); + return appVersions; + }, + + /** + * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist + * versionRange element. + * @param versionRangeElement + * The versionRange blocklist element. + * @returns A JS object with the following properties: + * "minVersion" The minimum version in a version range (default = null). + * "maxVersion" The maximum version in a version range (default = null). + */ + getBlocklistVersionRange: function(versionRangeElement) { + var minVersion = null; + var maxVersion = null; + if (!versionRangeElement) + return { minVersion: minVersion, maxVersion: maxVersion }; + + if (versionRangeElement.hasAttribute("minVersion")) + minVersion = versionRangeElement.getAttribute("minVersion"); + if (versionRangeElement.hasAttribute("maxVersion")) + maxVersion = versionRangeElement.getAttribute("maxVersion"); + + return { minVersion: minVersion, maxVersion: maxVersion }; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]); diff --git a/toolkit/components/blocklist/nsBlocklistServiceContent.js b/toolkit/components/blocklist/nsBlocklistServiceContent.js new file mode 100644 index 000000000..1752924b5 --- /dev/null +++ b/toolkit/components/blocklist/nsBlocklistServiceContent.js @@ -0,0 +1,113 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +const kMissingAPIMessage = "Unsupported blocklist call in the child process." + +/* + * A lightweight blocklist proxy for the content process that traps plugin + * related blocklist checks and forwards them to the parent. This interface is + * primarily designed to insure overlays work.. it does not control plugin + * or addon loading. + */ + +function Blocklist() { + this.init(); +} + +Blocklist.prototype = { + classID: Components.ID("{e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d}"), + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsIBlocklistService]), + + init: function() { + Services.cpmm.addMessageListener("Blocklist:blocklistInvalidated", this); + Services.obs.addObserver(this, "xpcom-shutdown", false); + }, + + uninit: function() { + Services.cpmm.removeMessageListener("Blocklist:blocklistInvalidated", this); + Services.obs.removeObserver(this, "xpcom-shutdown", false); + }, + + observe: function(aSubject, aTopic, aData) { + switch (aTopic) { + case "xpcom-shutdown": + this.uninit(); + break; + } + }, + + // Message manager message handlers + receiveMessage: function(aMsg) { + switch (aMsg.name) { + case "Blocklist:blocklistInvalidated": + Services.obs.notifyObservers(null, "blocklist-updated", null); + Services.cpmm.sendAsyncMessage("Blocklist:content-blocklist-updated"); + break; + default: + throw new Error("Unknown blocklist message received from content: " + aMsg.name); + } + }, + + /* + * A helper that queries key data from a plugin or addon object + * and generates a simple data wrapper suitable for ipc. We hand + * these directly to the nsBlockListService in the parent which + * doesn't query for much.. allowing us to get away with this. + */ + flattenObject: function(aTag) { + // Based on debugging the nsBlocklistService, these are the props the + // parent side will check on our objects. + let props = ["name", "description", "filename", "version"]; + let dataWrapper = {}; + for (let prop of props) { + dataWrapper[prop] = aTag[prop]; + } + return dataWrapper; + }, + + // We support the addon methods here for completeness, but content currently + // only calls getPluginBlocklistState. + + isAddonBlocklisted: function(aAddon, aAppVersion, aToolkitVersion) { + return true; + }, + + getAddonBlocklistState: function(aAddon, aAppVersion, aToolkitVersion) { + return Components.interfaces.nsIBlocklistService.STATE_BLOCKED; + }, + + // There are a few callers in layout that rely on this. + getPluginBlocklistState: function(aPluginTag, aAppVersion, aToolkitVersion) { + return Services.cpmm.sendSyncMessage("Blocklist:getPluginBlocklistState", { + addonData: this.flattenObject(aPluginTag), + appVersion: aAppVersion, + toolkitVersion: aToolkitVersion + })[0]; + }, + + getAddonBlocklistURL: function(aAddon, aAppVersion, aToolkitVersion) { + throw new Error(kMissingAPIMessage); + }, + + getPluginBlocklistURL: function(aPluginTag) { + throw new Error(kMissingAPIMessage); + }, + + getPluginInfoURL: function(aPluginTag) { + throw new Error(kMissingAPIMessage); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]); diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index e0b412428..8c065dc73 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -17,6 +17,7 @@ DIRS += [ 'alerts', 'apppicker', 'asyncshutdown', + 'blocklist', 'commandlines', 'console', 'contentprefs', diff --git a/toolkit/mozapps/extensions/extensions.manifest b/toolkit/mozapps/extensions/extensions.manifest index 14aca3ff6..7efb74a9d 100644 --- a/toolkit/mozapps/extensions/extensions.manifest +++ b/toolkit/mozapps/extensions/extensions.manifest @@ -1,7 +1,3 @@ -component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js -contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e} -category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1 -category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400 component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa} category update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400 diff --git a/toolkit/mozapps/extensions/moz.build b/toolkit/mozapps/extensions/moz.build index 19f449210..ca04d74a0 100644 --- a/toolkit/mozapps/extensions/moz.build +++ b/toolkit/mozapps/extensions/moz.build @@ -25,7 +25,6 @@ EXTRA_COMPONENTS += [ EXTRA_PP_COMPONENTS += [ 'extensions.manifest', - 'nsBlocklistService.js', ] EXTRA_JS_MODULES += [ diff --git a/toolkit/mozapps/extensions/nsBlocklistService.js b/toolkit/mozapps/extensions/nsBlocklistService.js deleted file mode 100644 index 487dae8e5..000000000 --- a/toolkit/mozapps/extensions/nsBlocklistService.js +++ /dev/null @@ -1,1478 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); - -try { - // AddonManager.jsm doesn't allow itself to be imported in the child - // process. We're used in the child process (for now), so guard against - // this. - Components.utils.import("resource://gre/modules/AddonManager.jsm"); -} catch (e) { -} - -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", - "resource://gre/modules/UpdateChannel.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -const TOOLKIT_ID = "toolkit@mozilla.org" -const KEY_PROFILEDIR = "ProfD"; -const KEY_APPDIR = "XCurProcD"; -const FILE_BLOCKLIST = "blocklist.xml"; -const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer"; -const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; -const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; -const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; -const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; -const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level"; -const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal"; -const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; -const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI"; -const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; -const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale"; -const PREF_APP_DISTRIBUTION = "distribution.id"; -const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; -const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; -const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; -const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" -const UNKNOWN_XPCOM_ABI = "unknownABI"; -const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul" -const DEFAULT_SEVERITY = 3; -const DEFAULT_LEVEL = 2; -const MAX_BLOCK_LEVEL = 3; -const SEVERITY_OUTDATED = 0; -const VULNERABILITYSTATUS_NONE = 0; -const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1; -const VULNERABILITYSTATUS_NO_UPDATE = 2; - -const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"]; - -var gLoggingEnabled = null; -var gBlocklistEnabled = true; -var gBlocklistLevel = DEFAULT_LEVEL; - -XPCOMUtils.defineLazyServiceGetter(this, "gConsole", - "@mozilla.org/consoleservice;1", - "nsIConsoleService"); - -XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker", - "@mozilla.org/xpcom/version-comparator;1", - "nsIVersionComparator"); - -XPCOMUtils.defineLazyServiceGetter(this, "gCertBlocklistService", - "@mozilla.org/security/certblocklist;1", - "nsICertBlocklist"); - -XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() { - return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService). - QueryInterface(Ci.nsIPrefBranch); -}); - -XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() { - return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo). - QueryInterface(Ci.nsIXULRuntime); -}); - -XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() { - let abi = null; - try { - abi = gApp.XPCOMABI; - } - catch (e) { - LOG("BlockList Global gABI: XPCOM ABI unknown."); - } -#ifdef XP_MACOSX - // Mac universal build should report a different ABI than either macppc - // or mactel. - let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. - getService(Ci.nsIMacUtils); - - if (macutils.isUniversalBinary) - abi += "-u-" + macutils.architecturesInBinary; -#endif - return abi; -}); - -XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() { - let osVersion; - let sysInfo = Cc["@mozilla.org/system-info;1"]. - getService(Ci.nsIPropertyBag2); - try { - osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); - } - catch (e) { - LOG("BlockList Global gOSVersion: OS Version unknown."); - } - - if (osVersion) { - try { - osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; - } - catch (e) { - // Not all platforms have a secondary widget library, so an error is nothing to worry about. - } - osVersion = encodeURIComponent(osVersion); - } - return osVersion; -}); - -// shared code for suppressing bad cert dialogs -XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() { - let temp = { }; - Components.utils.import("resource://gre/modules/CertUtils.jsm", temp); - return temp; -}); - -/** - * Logs a string to the error console. - * @param string - * The string to write to the error console.. - */ -function LOG(string) { - if (gLoggingEnabled) { - dump("*** " + string + "\n"); - gConsole.logStringMessage(string); - } -} - -/** - * Gets a preference value, handling the case where there is no default. - * @param func - * The name of the preference function to call, on nsIPrefBranch - * @param preference - * The name of the preference - * @param defaultValue - * The default value to return in the event the preference has - * no setting - * @returns The value of the preference, or undefined if there was no - * user or default value. - */ -function getPref(func, preference, defaultValue) { - try { - return gPref[func](preference); - } - catch (e) { - } - return defaultValue; -} - -/** - * Constructs a URI to a spec. - * @param spec - * The spec to construct a URI to - * @returns The nsIURI constructed. - */ -function newURI(spec) { - var ioServ = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - return ioServ.newURI(spec, null, null); -} - -// Restarts the application checking in with observers first -function restartApp() { - // Notify all windows that an application quit has been requested. - var os = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); - var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. - createInstance(Ci.nsISupportsPRBool); - os.notifyObservers(cancelQuit, "quit-application-requested", null); - - // Something aborted the quit process. - if (cancelQuit.data) - return; - - var as = Cc["@mozilla.org/toolkit/app-startup;1"]. - getService(Ci.nsIAppStartup); - as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); -} - -/** - * Checks whether this blocklist element is valid for the current OS and ABI. - * If the element has an "os" attribute then the current OS must appear in - * its comma separated list for the element to be valid. Similarly for the - * xpcomabi attribute. - */ -function matchesOSABI(blocklistElement) { - if (blocklistElement.hasAttribute("os")) { - var choices = blocklistElement.getAttribute("os").split(","); - if (choices.length > 0 && choices.indexOf(gApp.OS) < 0) - return false; - } - - if (blocklistElement.hasAttribute("xpcomabi")) { - choices = blocklistElement.getAttribute("xpcomabi").split(","); - if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0) - return false; - } - - return true; -} - -/** - * Gets the current value of the locale. It's possible for this preference to - * be localized, so we have to do a little extra work here. Similar code - * exists in nsHttpHandler.cpp when building the UA string. - */ -function getLocale() { - try { - // Get the default branch - var defaultPrefs = gPref.getDefaultBranch(null); - return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE, - Ci.nsIPrefLocalizedString).data; - } catch (e) {} - - return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE); -} - -/* Get the distribution pref values, from defaults only */ -function getDistributionPrefValue(aPrefName) { - var prefValue = "default"; - - var defaults = gPref.getDefaultBranch(null); - try { - prefValue = defaults.getCharPref(aPrefName); - } catch (e) { - // use default when pref not found - } - - return prefValue; -} - -/** - * Parse a string representation of a regular expression. Needed because we - * use the /pattern/flags form (because it's detectable), which is only - * supported as a literal in JS. - * - * @param aStr - * String representation of regexp - * @return RegExp instance - */ -function parseRegExp(aStr) { - let lastSlash = aStr.lastIndexOf("/"); - let pattern = aStr.slice(1, lastSlash); - let flags = aStr.slice(lastSlash + 1); - return new RegExp(pattern, flags); -} - -/** - * Helper function to test if the blockEntry matches with the plugin. - * - * @param blockEntry - * The plugin blocklist entries to compare against. - * @param plugin - * The nsIPluginTag to get the blocklist state for. - * @returns True if the blockEntry matches the plugin, false otherwise. - */ -function matchesAllPluginNames(blockEntry, plugin) { - for (let name in blockEntry.matches) { - if (!(name in plugin) || - typeof(plugin[name]) != "string" || - !blockEntry.matches[name].test(plugin[name])) { - return false; - } - } - return true; -} - -/** - * Manages the Blocklist. The Blocklist is a representation of the contents of - * blocklist.xml and allows us to remotely disable / re-enable blocklisted - * items managed by the Extension Manager with an item's appDisabled property. - * It also blocklists plugins with data from blocklist.xml. - */ - -function Blocklist() { - Services.obs.addObserver(this, "xpcom-shutdown", false); - Services.obs.addObserver(this, "sessionstore-windows-restored", false); - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); - gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), - MAX_BLOCK_LEVEL); - gPref.addObserver("extensions.blocklist.", this, false); - gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false); - this.wrappedJSObject = this; -} - -Blocklist.prototype = { - /** - * Extension ID -> array of Version Ranges - * Each value in the version range array is a JS Object that has the - * following properties: - * "minVersion" The minimum version in a version range (default = 0) - * "maxVersion" The maximum version in a version range (default = *) - * "targetApps" Application ID -> array of Version Ranges - * (default = current application ID) - * Each value in the version range array is a JS Object that - * has the following properties: - * "minVersion" The minimum version in a version range - * (default = 0) - * "maxVersion" The maximum version in a version range - * (default = *) - */ - _addonEntries: null, - _pluginEntries: null, - - observe: function Blocklist_observe(aSubject, aTopic, aData) { - switch (aTopic) { - case "xpcom-shutdown": - Services.obs.removeObserver(this, "xpcom-shutdown"); - gPref.removeObserver("extensions.blocklist.", this); - gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this); - break; - case "nsPref:changed": - switch (aData) { - case PREF_EM_LOGGING_ENABLED: - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - break; - case PREF_BLOCKLIST_ENABLED: - gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); - this._loadBlocklist(); - this._blocklistUpdated(null, null); - break; - case PREF_BLOCKLIST_LEVEL: - gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), - MAX_BLOCK_LEVEL); - this._blocklistUpdated(null, null); - break; - } - break; - case "sessionstore-windows-restored": - Services.obs.removeObserver(this, "sessionstore-windows-restored"); - this._preloadBlocklist(); - break; - } - }, - - /* See nsIBlocklistService */ - isAddonBlocklisted: function Blocklist_isAddonBlocklisted(addon, appVersion, toolkitVersion) { - return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) == - Ci.nsIBlocklistService.STATE_BLOCKED; - }, - - /* See nsIBlocklistService */ - getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) { - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - return this._getAddonBlocklistState(addon, this._addonEntries, - appVersion, toolkitVersion); - }, - - /** - * Private version of getAddonBlocklistState that allows the caller to pass in - * the add-on blocklist entries to compare against. - * - * @param id - * The ID of the item to get the blocklist state for. - * @param version - * The version of the item to get the blocklist state for. - * @param addonEntries - * The add-on blocklist entries to compare against. - * @param appVersion - * The application version to compare to, will use the current - * version if null. - * @param toolkitVersion - * The toolkit version to compare to, will use the current version if - * null. - * @returns The blocklist state for the item, one of the STATE constants as - * defined in nsIBlocklistService. - */ - _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(addon, - addonEntries, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - if (!appVersion) - appVersion = gApp.version; - if (!toolkitVersion) - toolkitVersion = gApp.platformVersion; - - var blItem = this._findMatchingAddonEntry(addonEntries, addon); - if (!blItem) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - for (let currentblItem of blItem.versions) { - if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) - return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED : - Ci.nsIBlocklistService.STATE_SOFTBLOCKED; - } - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - }, - - /** - * Returns the set of prefs of the add-on stored in the blocklist file - * (probably to revert them on disabling). - * @param addon - * The add-on whose to-be-reset prefs are to be found. - */ - _getAddonPrefs: function Blocklist_getAddonPrefs(addon) { - let entry = this._findMatchingAddonEntry(this._addonEntries, addon); - return entry.prefs.slice(0); - }, - - _findMatchingAddonEntry: function Blocklist_findMatchingAddonEntry(aAddonEntries, - aAddon) { - if (!aAddon) - return null; - // Returns true if the params object passes the constraints set by entry. - // (For every non-null property in entry, the same key must exist in - // params and value must be the same) - function checkEntry(entry, params) { - for (let [key, value] of entry) { - if (value === null || value === undefined) - continue; - if (params[key]) { - if (value instanceof RegExp) { - if (!value.test(params[key])) { - return false; - } - } else if (value !== params[key]) { - return false; - } - } else { - return false; - } - } - return true; - } - - let params = {}; - for (let filter of EXTENSION_BLOCK_FILTERS) { - params[filter] = aAddon[filter]; - } - if (params.creator) - params.creator = params.creator.name; - for (let entry of aAddonEntries) { - if (checkEntry(entry.attributes, params)) { - return entry; - } - } - return null; - }, - - /* See nsIBlocklistService */ - getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(addon, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return ""; - - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - - let blItem = this._findMatchingAddonEntry(this._addonEntries, addon); - if (!blItem || !blItem.blockID) - return null; - - return this._createBlocklistURL(blItem.blockID); - }, - - _createBlocklistURL: function Blocklist_createBlocklistURL(id) { - let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); - url = url.replace(/%blockID%/g, id); - - return url; - }, - - notify: function Blocklist_notify(aTimer) { - if (!gBlocklistEnabled) - return; - - try { - var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); - } - catch (e) { - LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + - " is missing!"); - return; - } - - var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0); - var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1); - var daysSinceLastPing = 0; - if (pingCountVersion == 0) { - daysSinceLastPing = "new"; - } - else { - // Seconds in one day is used because nsIUpdateTimerManager stores the - // last update time in seconds. - let secondsInDay = 60 * 60 * 24; - let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0); - if (lastUpdateTime == 0) { - daysSinceLastPing = "invalid"; - } - else { - let now = Math.round(Date.now() / 1000); - daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay); - } - - if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") { - pingCountVersion = pingCountTotal = "invalid"; - } - } - - if (pingCountVersion < 1) - pingCountVersion = 1; - if (pingCountTotal < 1) - pingCountTotal = 1; - - dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); - dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); - dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name); - dsURI = dsURI.replace(/%VERSION%/g, gApp.version); - dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID); - dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI); - dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion); - dsURI = dsURI.replace(/%LOCALE%/g, getLocale()); - dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get()); - dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion); - dsURI = dsURI.replace(/%DISTRIBUTION%/g, - getDistributionPrefValue(PREF_APP_DISTRIBUTION)); - dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g, - getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION)); - dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion); - dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal); - dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing); - dsURI = dsURI.replace(/\+/g, "%2B"); - - // Under normal operations it will take around 5,883,516 years before the - // preferences used to store pingCountVersion and pingCountTotal will rollover - // so this code doesn't bother trying to do the "right thing" here. - if (pingCountVersion != "invalid") { - pingCountVersion++; - if (pingCountVersion > 2147483647) { - // Rollover to -1 if the value is greater than what is support by an - // integer preference. The -1 indicates that the counter has been reset. - pingCountVersion = -1; - } - gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion); - } - - if (pingCountTotal != "invalid") { - pingCountTotal++; - if (pingCountTotal > 2147483647) { - // Rollover to 1 if the value is greater than what is support by an - // integer preference. - pingCountTotal = -1; - } - gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal); - } - - // Verify that the URI is valid - try { - var uri = newURI(dsURI); - } - catch (e) { - LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" + - "for: " + dsURI + ", error: " + e); - return; - } - - LOG("Blocklist::notify: Requesting " + uri.spec); - var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. - createInstance(Ci.nsIXMLHttpRequest); - request.open("GET", uri.spec, true); - request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); - request.overrideMimeType("text/xml"); - request.setRequestHeader("Cache-Control", "no-cache"); - request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); - - var self = this; - request.addEventListener("error", function errorEventListener(event) { - self.onXMLError(event); }, false); - request.addEventListener("load", function loadEventListener(event) { - self.onXMLLoad(event); }, false); - request.send(null); - - // When the blocklist loads we need to compare it to the current copy so - // make sure we have loaded it. - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - }, - - onXMLLoad: Task.async(function* (aEvent) { - let request = aEvent.target; - try { - gCertUtils.checkCert(request.channel); - } - catch (e) { - LOG("Blocklist::onXMLLoad: " + e); - return; - } - let responseXML = request.responseXML; - if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || - (request.status != 200 && request.status != 0)) { - LOG("Blocklist::onXMLLoad: there was an error during load"); - return; - } - - var oldAddonEntries = this._addonEntries; - var oldPluginEntries = this._pluginEntries; - this._addonEntries = []; - this._pluginEntries = []; - - this._loadBlocklistFromString(request.responseText); - this._blocklistUpdated(oldAddonEntries, oldPluginEntries); - - try { - let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); - yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"}); - } catch (e) { - LOG("Blocklist::onXMLLoad: " + e); - } - }), - - onXMLError: function Blocklist_onXMLError(aEvent) { - try { - var request = aEvent.target; - // the following may throw (e.g. a local file or timeout) - var status = request.status; - } - catch (e) { - request = aEvent.target.channel.QueryInterface(Ci.nsIRequest); - status = request.status; - } - var statusText = "nsIXMLHttpRequest channel unavailable"; - // When status is 0 we don't have a valid channel. - if (status != 0) { - try { - statusText = request.statusText; - } catch (e) { - } - } - LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + - statusText); - }, - - /** - * Finds the newest blocklist file from the application and the profile and - * load it or does nothing if neither exist. - */ - _loadBlocklist: function Blocklist_loadBlocklist() { - this._addonEntries = []; - this._pluginEntries = []; - var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); - if (profFile.exists()) { - this._loadBlocklistFromFile(profFile); - return; - } - var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - if (appFile.exists()) { - this._loadBlocklistFromFile(appFile); - return; - } - LOG("Blocklist::_loadBlocklist: no XML File found"); - }, - - /** -# The blocklist XML file looks something like this: -# -# -# -# -# -# accessibility.accesskeycausesactivation -# accessibility.blockautorefresh -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# AkHVNA== -# -# -# - */ - - _loadBlocklistFromFile: function Blocklist_loadBlocklistFromFile(file) { - if (!gBlocklistEnabled) { - LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); - return; - } - - let telemetry = Services.telemetry; - - if (this._isBlocklistPreloaded()) { - telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false); - this._loadBlocklistFromString(this._preloadedBlocklistContent); - delete this._preloadedBlocklistContent; - return; - } - - if (!file.exists()) { - LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path); - return; - } - - telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true); - - let text = ""; - let fstream = null; - let cstream = null; - - try { - fstream = Components.classes["@mozilla.org/network/file-input-stream;1"] - .createInstance(Components.interfaces.nsIFileInputStream); - cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - - fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); - cstream.init(fstream, "UTF-8", 0, 0); - - let str = {}; - let read = 0; - - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - text += str.value; - } while (read != 0); - } catch (e) { - LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e); - } finally { - if (cstream) - cstream.close(); - if (fstream) - fstream.close(); - } - - if (text) - this._loadBlocklistFromString(text); - }, - - _isBlocklistLoaded: function() { - return this._addonEntries != null && this._pluginEntries != null; - }, - - _isBlocklistPreloaded: function() { - return this._preloadedBlocklistContent != null; - }, - - /* Used for testing */ - _clear: function() { - this._addonEntries = null; - this._pluginEntries = null; - this._preloadedBlocklistContent = null; - }, - - _preloadBlocklist: Task.async(function*() { - let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); - try { - yield this._preloadBlocklistFile(profPath); - return; - } catch (e) { - LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) - } - - var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - try{ - yield this._preloadBlocklistFile(appFile.path); - return; - } catch (e) { - LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) - } - - LOG("Blocklist::_preloadBlocklist: no XML File found"); - }), - - _preloadBlocklistFile: Task.async(function* (path){ - if (this._addonEntries) { - // The file has been already loaded. - return; - } - - if (!gBlocklistEnabled) { - LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled"); - return; - } - - let text = yield OS.File.read(path, { encoding: "utf-8" }); - - if (!this._addonEntries) { - // Store the content only if a sync load has not been performed in the meantime. - this._preloadedBlocklistContent = text; - } - }), - - _loadBlocklistFromString : function Blocklist_loadBlocklistFromString(text) { - try { - var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. - createInstance(Ci.nsIDOMParser); - var doc = parser.parseFromString(text, "text/xml"); - if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { - LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " + - "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" + - "Received: " + doc.documentElement.namespaceURI); - return; - } - - var childNodes = doc.documentElement.childNodes; - for (let element of childNodes) { - if (!(element instanceof Ci.nsIDOMElement)) - continue; - switch (element.localName) { - case "emItems": - this._addonEntries = this._processItemNodes(element.childNodes, "em", - this._handleEmItemNode); - break; - case "pluginItems": - this._pluginEntries = this._processItemNodes(element.childNodes, "plugin", - this._handlePluginItemNode); - break; - case "certItems": - this._processItemNodes(element.childNodes, "cert", - this._handleCertItemNode.bind(this)); - break; - default: - Services.obs.notifyObservers(element, - "blocklist-data-" + element.localName, - null); - } - } - gCertBlocklistService.saveEntries(); - } - catch (e) { - LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); - return; - } - }, - - _processItemNodes: function Blocklist_processItemNodes(itemNodes, prefix, handler) { - var result = []; - var itemName = prefix + "Item"; - for (var i = 0; i < itemNodes.length; ++i) { - var blocklistElement = itemNodes.item(i); - if (!(blocklistElement instanceof Ci.nsIDOMElement) || - blocklistElement.localName != itemName) - continue; - - handler(blocklistElement, result); - } - return result; - }, - - _handleCertItemNode: function Blocklist_handleCertItemNode(blocklistElement, - result) { - let issuer = blocklistElement.getAttribute("issuerName"); - for (let snElement of blocklistElement.children) { - try { - gCertBlocklistService.revokeCertByIssuerAndSerial(issuer, snElement.textContent); - } catch (e) { - // we want to keep trying other elements since missing all items - // is worse than missing one - LOG("Blocklist::_handleCertItemNode: Error adding revoked cert " + e); - } - } - }, - - _handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) { - if (!matchesOSABI(blocklistElement)) - return; - - let blockEntry = { - versions: [], - prefs: [], - blockID: null, - attributes: new Map() - // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes - }; - - // Any filter starting with '/' is interpreted as a regex. So if an attribute - // starts with a '/' it must be checked via a regex. - function regExpCheck(attr) { - return attr.startsWith("/") ? parseRegExp(attr) : attr; - } - - for (let filter of EXTENSION_BLOCK_FILTERS) { - let attr = blocklistElement.getAttribute(filter); - if (attr) - blockEntry.attributes.set(filter, regExpCheck(attr)); - } - - var childNodes = blocklistElement.childNodes; - - for (let x = 0; x < childNodes.length; x++) { - var childElement = childNodes.item(x); - if (!(childElement instanceof Ci.nsIDOMElement)) - continue; - if (childElement.localName === "prefs") { - let prefElements = childElement.childNodes; - for (let i = 0; i < prefElements.length; i++) { - let prefElement = prefElements.item(i); - if (!(prefElement instanceof Ci.nsIDOMElement) || - prefElement.localName !== "pref") - continue; - blockEntry.prefs.push(prefElement.textContent); - } - } - else if (childElement.localName === "versionRange") - blockEntry.versions.push(new BlocklistItemData(childElement)); - } - // if only the extension ID is specified block all versions of the - // extension for the current application. - if (blockEntry.versions.length == 0) - blockEntry.versions.push(new BlocklistItemData(null)); - - blockEntry.blockID = blocklistElement.getAttribute("blockID"); - - result.push(blockEntry); - }, - - _handlePluginItemNode: function Blocklist_handlePluginItemNode(blocklistElement, result) { - if (!matchesOSABI(blocklistElement)) - return; - - var matchNodes = blocklistElement.childNodes; - var blockEntry = { - matches: {}, - versions: [], - blockID: null, - infoURL: null, - }; - var hasMatch = false; - for (var x = 0; x < matchNodes.length; ++x) { - var matchElement = matchNodes.item(x); - if (!(matchElement instanceof Ci.nsIDOMElement)) - continue; - if (matchElement.localName == "match") { - var name = matchElement.getAttribute("name"); - var exp = matchElement.getAttribute("exp"); - try { - blockEntry.matches[name] = new RegExp(exp, "m"); - hasMatch = true; - } catch (e) { - // Ignore invalid regular expressions - } - } - if (matchElement.localName == "versionRange") { - blockEntry.versions.push(new BlocklistItemData(matchElement)); - } - else if (matchElement.localName == "infoURL") { - blockEntry.infoURL = matchElement.textContent; - } - } - // Plugin entries require *something* to match to an actual plugin - if (!hasMatch) - return; - // Add a default versionRange if there wasn't one specified - if (blockEntry.versions.length == 0) - blockEntry.versions.push(new BlocklistItemData(null)); - - blockEntry.blockID = blocklistElement.getAttribute("blockID"); - - result.push(blockEntry); - }, - - /* See nsIBlocklistService */ - getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, - appVersion, toolkitVersion) { - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - return this._getPluginBlocklistState(plugin, this._pluginEntries, - appVersion, toolkitVersion); - }, - - /** - * Private version of getPluginBlocklistState that allows the caller to pass in - * the plugin blocklist entries. - * - * @param plugin - * The nsIPluginTag to get the blocklist state for. - * @param pluginEntries - * The plugin blocklist entries to compare against. - * @param appVersion - * The application version to compare to, will use the current - * version if null. - * @param toolkitVersion - * The toolkit version to compare to, will use the current version if - * null. - * @returns The blocklist state for the item, one of the STATE constants as - * defined in nsIBlocklistService. - */ - _getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, - pluginEntries, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - if (!appVersion) - appVersion = gApp.version; - if (!toolkitVersion) - toolkitVersion = gApp.platformVersion; - - for each (var blockEntry in pluginEntries) { - var matchFailed = false; - for (var name in blockEntry.matches) { - if (!(name in plugin) || - typeof(plugin[name]) != "string" || - !blockEntry.matches[name].test(plugin[name])) { - matchFailed = true; - break; - } - } - - if (matchFailed) - continue; - - for (let blockEntryVersion of blockEntry.versions) { - if (blockEntryVersion.includesItem(plugin.version, appVersion, - toolkitVersion)) { - if (blockEntryVersion.severity >= gBlocklistLevel) - return Ci.nsIBlocklistService.STATE_BLOCKED; - if (blockEntryVersion.severity == SEVERITY_OUTDATED) { - let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus; - if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE) - return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE; - if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE) - return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE; - return Ci.nsIBlocklistService.STATE_OUTDATED; - } - return Ci.nsIBlocklistService.STATE_SOFTBLOCKED; - } - } - } - - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - }, - - /** - * Get the matching blocklist entry for the passed plugin, if - * available. - * @param plugin The plugin to find the block entry for. - * @returns The block entry which matches the passed plugin, null - * otherwise. - */ - _getPluginBlockEntry: function (plugin) { - if (!gBlocklistEnabled) - return null; - - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - - for each (let blockEntry in this._pluginEntries) { - if (matchesAllPluginNames(blockEntry, plugin)) { - return blockEntry; - } - } - - return null; - }, - - /* See nsIBlocklistService */ - getPluginBlocklistURL: function Blocklist_getPluginBlocklistURL(plugin) { - let blockEntry = this._getPluginBlockEntry(plugin); - if (!blockEntry || !blockEntry.blockID) { - return null; - } - - return this._createBlocklistURL(blockEntry.blockID); - }, - - /* See nsIBlocklistService */ - getPluginInfoURL: function (plugin) { - let blockEntry = this._getPluginBlockEntry(plugin); - if (!blockEntry || !blockEntry.blockID) { - return null; - } - - return blockEntry.infoURL; - }, - - _blocklistUpdated: function Blocklist_blocklistUpdated(oldAddonEntries, oldPluginEntries) { - var addonList = []; - - // A helper function that reverts the prefs passed to default values. - function resetPrefs(prefs) { - for (let pref of prefs) - gPref.clearUserPref(pref); - } - var self = this; - const types = ["extension", "theme", "locale", "dictionary", "service"]; - AddonManager.getAddonsByTypes(types, function blocklistUpdated_getAddonsByTypes(addons) { - - for (let addon of addons) { - let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED; - if (oldAddonEntries) - oldState = self._getAddonBlocklistState(addon, oldAddonEntries); - let state = self.getAddonBlocklistState(addon); - - LOG("Blocklist state for " + addon.id + " changed from " + - oldState + " to " + state); - - // We don't want to re-warn about add-ons - if (state == oldState) - continue; - - if (state === Ci.nsIBlocklistService.STATE_BLOCKED) { - // It's a hard block. We must reset certain preferences. - let prefs = self._getAddonPrefs(addon); - resetPrefs(prefs); - } - - // Ensure that softDisabled is false if the add-on is not soft blocked - if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - addon.softDisabled = false; - - // Don't warn about add-ons becoming unblocked. - if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED) - continue; - - // If an add-on has dropped from hard to soft blocked just mark it as - // soft disabled and don't warn about it. - if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && - oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { - addon.softDisabled = true; - continue; - } - - // If the add-on is already disabled for some reason then don't warn - // about it - if (!addon.isActive) - continue; - - addonList.push({ - name: addon.name, - version: addon.version, - icon: addon.iconURL, - disable: false, - blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, - item: addon, - url: self.getAddonBlocklistURL(addon), - }); - } - - AddonManagerPrivate.updateAddonAppDisabledStates(); - - var phs = Cc["@mozilla.org/plugin/host;1"]. - getService(Ci.nsIPluginHost); - var plugins = phs.getPluginTags(); - - for (let plugin of plugins) { - let oldState = -1; - if (oldPluginEntries) - oldState = self._getPluginBlocklistState(plugin, oldPluginEntries); - let state = self.getPluginBlocklistState(plugin); - LOG("Blocklist state for " + plugin.name + " changed from " + - oldState + " to " + state); - // We don't want to re-warn about items - if (state == oldState) - continue; - - if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { - if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; - } - else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { - if (state == Ci.nsIBlocklistService.STATE_OUTDATED) { - gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true); - } - else if (state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE && - state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { - addonList.push({ - name: plugin.name, - version: plugin.version, - icon: "chrome://mozapps/skin/plugins/pluginGeneric.png", - disable: false, - blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, - item: plugin, - url: self.getPluginBlocklistURL(plugin), - }); - } - } - } - - if (addonList.length == 0) { - Services.obs.notifyObservers(self, "blocklist-updated", ""); - return; - } - - if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) { - try { - let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"] - .getService(Ci.nsIBlocklistPrompt); - blockedPrompter.prompt(addonList); - } catch (e) { - LOG(e); - } - Services.obs.notifyObservers(self, "blocklist-updated", ""); - return; - } - - var args = { - restart: false, - list: addonList - }; - // This lets the dialog get the raw js object - args.wrappedJSObject = args; - - /* - Some tests run without UI, so the async code listens to a message - that can be sent programatically - */ - let applyBlocklistChanges = function blocklistUpdated_applyBlocklistChanges() { - for (let addon of addonList) { - if (!addon.disable) - continue; - - if (addon.item instanceof Ci.nsIPluginTag) - addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED; - else { - // This add-on is softblocked. - addon.item.softDisabled = true; - // We must revert certain prefs. - let prefs = self._getAddonPrefs(addon.item); - resetPrefs(prefs); - } - } - - if (args.restart) - restartApp(); - - Services.obs.notifyObservers(self, "blocklist-updated", ""); - Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed"); - } - - Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false); - - if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) { - applyBlocklistChanges(); - return; - } - - function blocklistUnloadHandler(event) { - if (event.target.location == URI_BLOCKLIST_DIALOG) { - applyBlocklistChanges(); - blocklistWindow.removeEventListener("unload", blocklistUnloadHandler); - } - } - - let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "", - "chrome,centerscreen,dialog,titlebar", args); - if (blocklistWindow) - blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false); - }); - }, - - classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsIBlocklistService, - Ci.nsITimerCallback]), -}; - -/** - * Helper for constructing a blocklist. - */ -function BlocklistItemData(versionRangeElement) { - var versionRange = this.getBlocklistVersionRange(versionRangeElement); - this.minVersion = versionRange.minVersion; - this.maxVersion = versionRange.maxVersion; - if (versionRangeElement && versionRangeElement.hasAttribute("severity")) - this.severity = versionRangeElement.getAttribute("severity"); - else - this.severity = DEFAULT_SEVERITY; - if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) { - this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus"); - } else { - this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE; - } - this.targetApps = { }; - var found = false; - - if (versionRangeElement) { - for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { - var targetAppElement = versionRangeElement.childNodes.item(i); - if (!(targetAppElement instanceof Ci.nsIDOMElement) || - targetAppElement.localName != "targetApplication") - continue; - found = true; - // default to the current application if id is not provided. - var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID; - this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement); - } - } - // Default to all versions of the current application when no targetApplication - // elements were found - if (!found) - this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); -} - -BlocklistItemData.prototype = { - /** - * Tests if a version of an item is included in the version range and target - * application information represented by this BlocklistItemData using the - * provided application and toolkit versions. - * @param version - * The version of the item being tested. - * @param appVersion - * The application version to test with. - * @param toolkitVersion - * The toolkit version to test with. - * @returns True if the version range covers the item version and application - * or toolkit version. - */ - includesItem: function BlocklistItemData_includesItem(version, appVersion, toolkitVersion) { - // Some platforms have no version for plugins, these don't match if there - // was a min/maxVersion provided - if (!version && (this.minVersion || this.maxVersion)) - return false; - - // Check if the item version matches - if (!this.matchesRange(version, this.minVersion, this.maxVersion)) - return false; - - // Check if the application version matches - if (this.matchesTargetRange(gApp.ID, appVersion)) - return true; - - // Check if the toolkit version matches - return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion); - }, - - /** - * Checks if a version is higher than or equal to the minVersion (if provided) - * and lower than or equal to the maxVersion (if provided). - * @param version - * The version to test. - * @param minVersion - * The minimum version. If null it is assumed that version is always - * larger. - * @param maxVersion - * The maximum version. If null it is assumed that version is always - * smaller. - */ - matchesRange: function BlocklistItemData_matchesRange(version, minVersion, maxVersion) { - if (minVersion && gVersionChecker.compare(version, minVersion) < 0) - return false; - if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0) - return false; - return true; - }, - - /** - * Tests if there is a matching range for the given target application id and - * version. - * @param appID - * The application ID to test for, may be for an application or toolkit - * @param appVersion - * The version of the application to test for. - * @returns True if this version range covers the application version given. - */ - matchesTargetRange: function BlocklistItemData_matchesTargetRange(appID, appVersion) { - var blTargetApp = this.targetApps[appID]; - if (!blTargetApp) - return false; - - for (let app of blTargetApp) { - if (this.matchesRange(appVersion, app.minVersion, app.maxVersion)) - return true; - } - - return false; - }, - - /** - * Retrieves a version range (e.g. minVersion and maxVersion) for a - * blocklist item's targetApplication element. - * @param targetAppElement - * A targetApplication blocklist element. - * @returns An array of JS objects with the following properties: - * "minVersion" The minimum version in a version range (default = null). - * "maxVersion" The maximum version in a version range (default = null). - */ - getBlocklistAppVersions: function BlocklistItemData_getBlocklistAppVersions(targetAppElement) { - var appVersions = [ ]; - - if (targetAppElement) { - for (var i = 0; i < targetAppElement.childNodes.length; ++i) { - var versionRangeElement = targetAppElement.childNodes.item(i); - if (!(versionRangeElement instanceof Ci.nsIDOMElement) || - versionRangeElement.localName != "versionRange") - continue; - appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); - } - } - // return minVersion = null and maxVersion = null if no specific versionRange - // elements were found - if (appVersions.length == 0) - appVersions.push(this.getBlocklistVersionRange(null)); - return appVersions; - }, - - /** - * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist - * versionRange element. - * @param versionRangeElement - * The versionRange blocklist element. - * @returns A JS object with the following properties: - * "minVersion" The minimum version in a version range (default = null). - * "maxVersion" The maximum version in a version range (default = null). - */ - getBlocklistVersionRange: function BlocklistItemData_getBlocklistVersionRange(versionRangeElement) { - var minVersion = null; - var maxVersion = null; - if (!versionRangeElement) - return { minVersion: minVersion, maxVersion: maxVersion }; - - if (versionRangeElement.hasAttribute("minVersion")) - minVersion = versionRangeElement.getAttribute("minVersion"); - if (versionRangeElement.hasAttribute("maxVersion")) - maxVersion = versionRangeElement.getAttribute("maxVersion"); - - return { minVersion: minVersion, maxVersion: maxVersion }; - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]); diff --git a/toolkit/mozapps/webextensions/extensions.manifest b/toolkit/mozapps/webextensions/extensions.manifest index 7ce20c3ec..2129012ab 100644 --- a/toolkit/mozapps/webextensions/extensions.manifest +++ b/toolkit/mozapps/webextensions/extensions.manifest @@ -1,10 +1,3 @@ -component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js process=main -contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e} process=main -category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1 process=main -component {e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d} nsBlocklistServiceContent.js process=content -contract @mozilla.org/extensions/blocklist;1 {e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d} process=content - -category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400 component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa} #ifndef MOZ_WIDGET_ANDROID diff --git a/toolkit/mozapps/webextensions/moz.build b/toolkit/mozapps/webextensions/moz.build index b92915ae8..30152676a 100644 --- a/toolkit/mozapps/webextensions/moz.build +++ b/toolkit/mozapps/webextensions/moz.build @@ -24,8 +24,6 @@ EXTRA_COMPONENTS += [ 'amInstallTrigger.js', 'amWebAPI.js', 'amWebInstallListener.js', - 'nsBlocklistService.js', - 'nsBlocklistServiceContent.js', ] EXTRA_PP_COMPONENTS += [ diff --git a/toolkit/mozapps/webextensions/nsBlocklistService.js b/toolkit/mozapps/webextensions/nsBlocklistService.js deleted file mode 100644 index 268c197fc..000000000 --- a/toolkit/mozapps/webextensions/nsBlocklistService.js +++ /dev/null @@ -1,1658 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); -Components.utils.import("resource://gre/modules/AppConstants.jsm"); - -try { - // AddonManager.jsm doesn't allow itself to be imported in the child - // process. We're used in the child process (for now), so guard against - // this. - Components.utils.import("resource://gre/modules/AddonManager.jsm"); - /* globals AddonManagerPrivate*/ -} catch (e) { -} - -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", - "resource://gre/modules/UpdateUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest", - "resource://gre/modules/ServiceRequest.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -const TOOLKIT_ID = "toolkit@mozilla.org"; -const KEY_PROFILEDIR = "ProfD"; -const KEY_APPDIR = "XCurProcD"; -const FILE_BLOCKLIST = "blocklist.xml"; -const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer"; -const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; -const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; -const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; -const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; -const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level"; -const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal"; -const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; -const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI"; -const PREF_ONECRL_VIA_AMO = "security.onecrl.via.amo"; -const PREF_BLOCKLIST_UPDATE_ENABLED = "services.blocklist.update_enabled"; -const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale"; -const PREF_APP_DISTRIBUTION = "distribution.id"; -const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; -const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; -const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; -const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" -const UNKNOWN_XPCOM_ABI = "unknownABI"; -const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul" -const DEFAULT_SEVERITY = 3; -const DEFAULT_LEVEL = 2; -const MAX_BLOCK_LEVEL = 3; -const SEVERITY_OUTDATED = 0; -const VULNERABILITYSTATUS_NONE = 0; -const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1; -const VULNERABILITYSTATUS_NO_UPDATE = 2; - -const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"]; - -var gLoggingEnabled = null; -var gBlocklistEnabled = true; -var gBlocklistLevel = DEFAULT_LEVEL; - -XPCOMUtils.defineLazyServiceGetter(this, "gConsole", - "@mozilla.org/consoleservice;1", - "nsIConsoleService"); - -XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker", - "@mozilla.org/xpcom/version-comparator;1", - "nsIVersionComparator"); - -XPCOMUtils.defineLazyServiceGetter(this, "gCertBlocklistService", - "@mozilla.org/security/certblocklist;1", - "nsICertBlocklist"); - -XPCOMUtils.defineLazyGetter(this, "gPref", function() { - return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService). - QueryInterface(Ci.nsIPrefBranch); -}); - -// From appinfo in Services.jsm. It is not possible to use the one in -// Services.jsm since it will not successfully QueryInterface nsIXULAppInfo in -// xpcshell tests due to other code calling Services.appinfo before the -// nsIXULAppInfo is created by the tests. -XPCOMUtils.defineLazyGetter(this, "gApp", function() { - let appinfo = Cc["@mozilla.org/xre/app-info;1"] - .getService(Ci.nsIXULRuntime); - try { - appinfo.QueryInterface(Ci.nsIXULAppInfo); - } catch (ex) { - // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). - if (!(ex instanceof Components.Exception) || - ex.result != Cr.NS_NOINTERFACE) - throw ex; - } - return appinfo; -}); - -XPCOMUtils.defineLazyGetter(this, "gABI", function() { - let abi = null; - try { - abi = gApp.XPCOMABI; - } - catch (e) { - LOG("BlockList Global gABI: XPCOM ABI unknown."); - } - - if (AppConstants.platform == "macosx") { - // Mac universal build should report a different ABI than either macppc - // or mactel. - let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. - getService(Ci.nsIMacUtils); - - if (macutils.isUniversalBinary) - abi += "-u-" + macutils.architecturesInBinary; - } - return abi; -}); - -XPCOMUtils.defineLazyGetter(this, "gOSVersion", function() { - let osVersion; - let sysInfo = Cc["@mozilla.org/system-info;1"]. - getService(Ci.nsIPropertyBag2); - try { - osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); - } - catch (e) { - LOG("BlockList Global gOSVersion: OS Version unknown."); - } - - if (osVersion) { - try { - osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; - } - catch (e) { - // Not all platforms have a secondary widget library, so an error is nothing to worry about. - } - osVersion = encodeURIComponent(osVersion); - } - return osVersion; -}); - -// shared code for suppressing bad cert dialogs -XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() { - let temp = { }; - Components.utils.import("resource://gre/modules/CertUtils.jsm", temp); - return temp; -}); - -/** - * Logs a string to the error console. - * @param string - * The string to write to the error console.. - */ -function LOG(string) { - if (gLoggingEnabled) { - dump("*** " + string + "\n"); - gConsole.logStringMessage(string); - } -} - -/** - * Gets a preference value, handling the case where there is no default. - * @param func - * The name of the preference function to call, on nsIPrefBranch - * @param preference - * The name of the preference - * @param defaultValue - * The default value to return in the event the preference has - * no setting - * @returns The value of the preference, or undefined if there was no - * user or default value. - */ -function getPref(func, preference, defaultValue) { - try { - return gPref[func](preference); - } - catch (e) { - } - return defaultValue; -} - -/** - * Constructs a URI to a spec. - * @param spec - * The spec to construct a URI to - * @returns The nsIURI constructed. - */ -function newURI(spec) { - var ioServ = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - return ioServ.newURI(spec, null, null); -} - -// Restarts the application checking in with observers first -function restartApp() { - // Notify all windows that an application quit has been requested. - var os = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); - var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. - createInstance(Ci.nsISupportsPRBool); - os.notifyObservers(cancelQuit, "quit-application-requested", null); - - // Something aborted the quit process. - if (cancelQuit.data) - return; - - var as = Cc["@mozilla.org/toolkit/app-startup;1"]. - getService(Ci.nsIAppStartup); - as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); -} - -/** - * Checks whether this blocklist element is valid for the current OS and ABI. - * If the element has an "os" attribute then the current OS must appear in - * its comma separated list for the element to be valid. Similarly for the - * xpcomabi attribute. - */ -function matchesOSABI(blocklistElement) { - if (blocklistElement.hasAttribute("os")) { - var choices = blocklistElement.getAttribute("os").split(","); - if (choices.length > 0 && choices.indexOf(gApp.OS) < 0) - return false; - } - - if (blocklistElement.hasAttribute("xpcomabi")) { - choices = blocklistElement.getAttribute("xpcomabi").split(","); - if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0) - return false; - } - - return true; -} - -/** - * Gets the current value of the locale. It's possible for this preference to - * be localized, so we have to do a little extra work here. Similar code - * exists in nsHttpHandler.cpp when building the UA string. - */ -function getLocale() { - try { - // Get the default branch - var defaultPrefs = gPref.getDefaultBranch(null); - return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE, - Ci.nsIPrefLocalizedString).data; - } catch (e) {} - - return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE); -} - -/* Get the distribution pref values, from defaults only */ -function getDistributionPrefValue(aPrefName) { - var prefValue = "default"; - - var defaults = gPref.getDefaultBranch(null); - try { - prefValue = defaults.getCharPref(aPrefName); - } catch (e) { - // use default when pref not found - } - - return prefValue; -} - -/** - * Parse a string representation of a regular expression. Needed because we - * use the /pattern/flags form (because it's detectable), which is only - * supported as a literal in JS. - * - * @param aStr - * String representation of regexp - * @return RegExp instance - */ -function parseRegExp(aStr) { - let lastSlash = aStr.lastIndexOf("/"); - let pattern = aStr.slice(1, lastSlash); - let flags = aStr.slice(lastSlash + 1); - return new RegExp(pattern, flags); -} - -/** - * Manages the Blocklist. The Blocklist is a representation of the contents of - * blocklist.xml and allows us to remotely disable / re-enable blocklisted - * items managed by the Extension Manager with an item's appDisabled property. - * It also blocklists plugins with data from blocklist.xml. - */ - -function Blocklist() { - Services.obs.addObserver(this, "xpcom-shutdown", false); - Services.obs.addObserver(this, "sessionstore-windows-restored", false); - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); - gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), - MAX_BLOCK_LEVEL); - gPref.addObserver("extensions.blocklist.", this, false); - gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false); - this.wrappedJSObject = this; - // requests from child processes come in here, see receiveMessage. - Services.ppmm.addMessageListener("Blocklist:getPluginBlocklistState", this); - Services.ppmm.addMessageListener("Blocklist:content-blocklist-updated", this); -} - -Blocklist.prototype = { - /** - * Extension ID -> array of Version Ranges - * Each value in the version range array is a JS Object that has the - * following properties: - * "minVersion" The minimum version in a version range (default = 0) - * "maxVersion" The maximum version in a version range (default = *) - * "targetApps" Application ID -> array of Version Ranges - * (default = current application ID) - * Each value in the version range array is a JS Object that - * has the following properties: - * "minVersion" The minimum version in a version range - * (default = 0) - * "maxVersion" The maximum version in a version range - * (default = *) - */ - _addonEntries: null, - _gfxEntries: null, - _pluginEntries: null, - - shutdown: function() { - Services.obs.removeObserver(this, "xpcom-shutdown"); - Services.ppmm.removeMessageListener("Blocklist:getPluginBlocklistState", this); - Services.ppmm.removeMessageListener("Blocklist:content-blocklist-updated", this); - gPref.removeObserver("extensions.blocklist.", this); - gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this); - }, - - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case "xpcom-shutdown": - this.shutdown(); - break; - case "nsPref:changed": - switch (aData) { - case PREF_EM_LOGGING_ENABLED: - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - break; - case PREF_BLOCKLIST_ENABLED: - gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); - this._loadBlocklist(); - this._blocklistUpdated(null, null); - break; - case PREF_BLOCKLIST_LEVEL: - gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), - MAX_BLOCK_LEVEL); - this._blocklistUpdated(null, null); - break; - } - break; - case "sessionstore-windows-restored": - Services.obs.removeObserver(this, "sessionstore-windows-restored"); - this._preloadBlocklist(); - break; - } - }, - - // Message manager message handlers - receiveMessage: function(aMsg) { - switch (aMsg.name) { - case "Blocklist:getPluginBlocklistState": - return this.getPluginBlocklistState(aMsg.data.addonData, - aMsg.data.appVersion, - aMsg.data.toolkitVersion); - case "Blocklist:content-blocklist-updated": - Services.obs.notifyObservers(null, "content-blocklist-updated", null); - break; - default: - throw new Error("Unknown blocklist message received from content: " + aMsg.name); - } - return undefined; - }, - - /* See nsIBlocklistService */ - isAddonBlocklisted: function(addon, appVersion, toolkitVersion) { - return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) == - Ci.nsIBlocklistService.STATE_BLOCKED; - }, - - /* See nsIBlocklistService */ - getAddonBlocklistState: function(addon, appVersion, toolkitVersion) { - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - return this._getAddonBlocklistState(addon, this._addonEntries, - appVersion, toolkitVersion); - }, - - /** - * Private version of getAddonBlocklistState that allows the caller to pass in - * the add-on blocklist entries to compare against. - * - * @param id - * The ID of the item to get the blocklist state for. - * @param version - * The version of the item to get the blocklist state for. - * @param addonEntries - * The add-on blocklist entries to compare against. - * @param appVersion - * The application version to compare to, will use the current - * version if null. - * @param toolkitVersion - * The toolkit version to compare to, will use the current version if - * null. - * @returns The blocklist state for the item, one of the STATE constants as - * defined in nsIBlocklistService. - */ - _getAddonBlocklistState: function(addon, addonEntries, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). - if (!appVersion && !gApp.version) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - if (!appVersion) - appVersion = gApp.version; - if (!toolkitVersion) - toolkitVersion = gApp.platformVersion; - - var blItem = this._findMatchingAddonEntry(addonEntries, addon); - if (!blItem) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - for (let currentblItem of blItem.versions) { - if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) - return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED : - Ci.nsIBlocklistService.STATE_SOFTBLOCKED; - } - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - }, - - /** - * Returns the set of prefs of the add-on stored in the blocklist file - * (probably to revert them on disabling). - * @param addon - * The add-on whose to-be-reset prefs are to be found. - */ - _getAddonPrefs: function(addon) { - let entry = this._findMatchingAddonEntry(this._addonEntries, addon); - return entry.prefs.slice(0); - }, - - _findMatchingAddonEntry: function(aAddonEntries, aAddon) { - if (!aAddon) - return null; - // Returns true if the params object passes the constraints set by entry. - // (For every non-null property in entry, the same key must exist in - // params and value must be the same) - function checkEntry(entry, params) { - for (let [key, value] of entry) { - if (value === null || value === undefined) - continue; - if (params[key]) { - if (value instanceof RegExp) { - if (!value.test(params[key])) { - return false; - } - } else if (value !== params[key]) { - return false; - } - } else { - return false; - } - } - return true; - } - - let params = {}; - for (let filter of EXTENSION_BLOCK_FILTERS) { - params[filter] = aAddon[filter]; - } - if (params.creator) - params.creator = params.creator.name; - for (let entry of aAddonEntries) { - if (checkEntry(entry.attributes, params)) { - return entry; - } - } - return null; - }, - - /* See nsIBlocklistService */ - getAddonBlocklistURL: function(addon, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return ""; - - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - - let blItem = this._findMatchingAddonEntry(this._addonEntries, addon); - if (!blItem || !blItem.blockID) - return null; - - return this._createBlocklistURL(blItem.blockID); - }, - - _createBlocklistURL: function(id) { - let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); - url = url.replace(/%blockID%/g, id); - - return url; - }, - - notify: function(aTimer) { - if (!gBlocklistEnabled) - return; - - try { - var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); - } - catch (e) { - LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + - " is missing!"); - return; - } - - var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0); - var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1); - var daysSinceLastPing = 0; - if (pingCountVersion == 0) { - daysSinceLastPing = "new"; - } - else { - // Seconds in one day is used because nsIUpdateTimerManager stores the - // last update time in seconds. - let secondsInDay = 60 * 60 * 24; - let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0); - if (lastUpdateTime == 0) { - daysSinceLastPing = "invalid"; - } - else { - let now = Math.round(Date.now() / 1000); - daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay); - } - - if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") { - pingCountVersion = pingCountTotal = "invalid"; - } - } - - if (pingCountVersion < 1) - pingCountVersion = 1; - if (pingCountTotal < 1) - pingCountTotal = 1; - - dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); - // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). - if (gApp.version) - dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); - dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name); - // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). - if (gApp.version) - dsURI = dsURI.replace(/%VERSION%/g, gApp.version); - dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID); - dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI); - dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion); - dsURI = dsURI.replace(/%LOCALE%/g, getLocale()); - dsURI = dsURI.replace(/%CHANNEL%/g, UpdateUtils.UpdateChannel); - dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion); - dsURI = dsURI.replace(/%DISTRIBUTION%/g, - getDistributionPrefValue(PREF_APP_DISTRIBUTION)); - dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g, - getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION)); - dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion); - dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal); - dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing); - dsURI = dsURI.replace(/\+/g, "%2B"); - - // Under normal operations it will take around 5,883,516 years before the - // preferences used to store pingCountVersion and pingCountTotal will rollover - // so this code doesn't bother trying to do the "right thing" here. - if (pingCountVersion != "invalid") { - pingCountVersion++; - if (pingCountVersion > 2147483647) { - // Rollover to -1 if the value is greater than what is support by an - // integer preference. The -1 indicates that the counter has been reset. - pingCountVersion = -1; - } - gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion); - } - - if (pingCountTotal != "invalid") { - pingCountTotal++; - if (pingCountTotal > 2147483647) { - // Rollover to 1 if the value is greater than what is support by an - // integer preference. - pingCountTotal = -1; - } - gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal); - } - - // Verify that the URI is valid - try { - var uri = newURI(dsURI); - } - catch (e) { - LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" + - "for: " + dsURI + ", error: " + e); - return; - } - - LOG("Blocklist::notify: Requesting " + uri.spec); - let request = new ServiceRequest(); - request.open("GET", uri.spec, true); - request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); - request.overrideMimeType("text/xml"); - request.setRequestHeader("Cache-Control", "no-cache"); - request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); - - request.addEventListener("error", event => this.onXMLError(event), false); - request.addEventListener("load", event => this.onXMLLoad(event), false); - request.send(null); - - // When the blocklist loads we need to compare it to the current copy so - // make sure we have loaded it. - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - }, - - onXMLLoad: Task.async(function*(aEvent) { - let request = aEvent.target; - try { - gCertUtils.checkCert(request.channel); - } - catch (e) { - LOG("Blocklist::onXMLLoad: " + e); - return; - } - let responseXML = request.responseXML; - if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || - (request.status != 200 && request.status != 0)) { - LOG("Blocklist::onXMLLoad: there was an error during load"); - return; - } - - var oldAddonEntries = this._addonEntries; - var oldPluginEntries = this._pluginEntries; - this._addonEntries = []; - this._gfxEntries = []; - this._pluginEntries = []; - - this._loadBlocklistFromString(request.responseText); - // We don't inform the users when the graphics blocklist changed at runtime. - // However addons and plugins blocking status is refreshed. - this._blocklistUpdated(oldAddonEntries, oldPluginEntries); - - try { - let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); - yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"}); - } catch (e) { - LOG("Blocklist::onXMLLoad: " + e); - } - }), - - onXMLError: function(aEvent) { - try { - var request = aEvent.target; - // the following may throw (e.g. a local file or timeout) - var status = request.status; - } - catch (e) { - request = aEvent.target.channel.QueryInterface(Ci.nsIRequest); - status = request.status; - } - var statusText = "nsIXMLHttpRequest channel unavailable"; - // When status is 0 we don't have a valid channel. - if (status != 0) { - try { - statusText = request.statusText; - } catch (e) { - } - } - LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + - statusText); - }, - - /** - * Finds the newest blocklist file from the application and the profile and - * load it or does nothing if neither exist. - */ - _loadBlocklist: function() { - this._addonEntries = []; - this._gfxEntries = []; - this._pluginEntries = []; - var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); - if (profFile.exists()) { - this._loadBlocklistFromFile(profFile); - return; - } - var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - if (appFile.exists()) { - this._loadBlocklistFromFile(appFile); - return; - } - LOG("Blocklist::_loadBlocklist: no XML File found"); - }, - - /** -# The blocklist XML file looks something like this: -# -# -# -# -# -# accessibility.accesskeycausesactivation -# accessibility.blockautorefresh -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# AkHVNA== -# -# -# -# -# -# - */ - - _loadBlocklistFromFile: function(file) { - if (!gBlocklistEnabled) { - LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); - return; - } - - let telemetry = Services.telemetry; - - if (this._isBlocklistPreloaded()) { - telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false); - this._loadBlocklistFromString(this._preloadedBlocklistContent); - delete this._preloadedBlocklistContent; - return; - } - - if (!file.exists()) { - LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path); - return; - } - - telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true); - - let text = ""; - let fstream = null; - let cstream = null; - - try { - fstream = Components.classes["@mozilla.org/network/file-input-stream;1"] - .createInstance(Components.interfaces.nsIFileInputStream); - cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - - fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); - cstream.init(fstream, "UTF-8", 0, 0); - - let str = {}; - let read = 0; - - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - text += str.value; - } while (read != 0); - } catch (e) { - LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e); - } finally { - if (cstream) - cstream.close(); - if (fstream) - fstream.close(); - } - - if (text) - this._loadBlocklistFromString(text); - }, - - _isBlocklistLoaded: function() { - return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null; - }, - - _isBlocklistPreloaded: function() { - return this._preloadedBlocklistContent != null; - }, - - /* Used for testing */ - _clear: function() { - this._addonEntries = null; - this._gfxEntries = null; - this._pluginEntries = null; - this._preloadedBlocklistContent = null; - }, - - _preloadBlocklist: Task.async(function*() { - let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); - try { - yield this._preloadBlocklistFile(profPath); - return; - } catch (e) { - LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) - } - - var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - try { - yield this._preloadBlocklistFile(appFile.path); - return; - } catch (e) { - LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) - } - - LOG("Blocklist::_preloadBlocklist: no XML File found"); - }), - - _preloadBlocklistFile: Task.async(function*(path) { - if (this._addonEntries) { - // The file has been already loaded. - return; - } - - if (!gBlocklistEnabled) { - LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled"); - return; - } - - let text = yield OS.File.read(path, { encoding: "utf-8" }); - - if (!this._addonEntries) { - // Store the content only if a sync load has not been performed in the meantime. - this._preloadedBlocklistContent = text; - } - }), - - _loadBlocklistFromString : function(text) { - try { - var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. - createInstance(Ci.nsIDOMParser); - var doc = parser.parseFromString(text, "text/xml"); - if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { - LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " + - "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" + - "Received: " + doc.documentElement.namespaceURI); - return; - } - - var populateCertBlocklist = getPref("getBoolPref", PREF_ONECRL_VIA_AMO, true); - - var childNodes = doc.documentElement.childNodes; - for (let element of childNodes) { - if (!(element instanceof Ci.nsIDOMElement)) - continue; - switch (element.localName) { - case "emItems": - this._addonEntries = this._processItemNodes(element.childNodes, "emItem", - this._handleEmItemNode); - break; - case "pluginItems": - this._pluginEntries = this._processItemNodes(element.childNodes, "pluginItem", - this._handlePluginItemNode); - break; - case "certItems": - if (populateCertBlocklist) { - this._processItemNodes(element.childNodes, "certItem", - this._handleCertItemNode.bind(this)); - } - break; - case "gfxItems": - // Parse as simple list of objects. - this._gfxEntries = this._processItemNodes(element.childNodes, "gfxBlacklistEntry", - this._handleGfxBlacklistNode); - break; - default: - LOG("Blocklist::_loadBlocklistFromString: ignored entries " + element.localName); - } - } - if (populateCertBlocklist) { - gCertBlocklistService.saveEntries(); - } - if (this._gfxEntries.length > 0) { - this._notifyObserversBlocklistGFX(); - } - } - catch (e) { - LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); - return; - } - }, - - _processItemNodes: function(itemNodes, itemName, handler) { - var result = []; - for (var i = 0; i < itemNodes.length; ++i) { - var blocklistElement = itemNodes.item(i); - if (!(blocklistElement instanceof Ci.nsIDOMElement) || - blocklistElement.localName != itemName) - continue; - - handler(blocklistElement, result); - } - return result; - }, - - _handleCertItemNode: function(blocklistElement, result) { - let issuer = blocklistElement.getAttribute("issuerName"); - if (issuer) { - for (let snElement of blocklistElement.children) { - try { - gCertBlocklistService.revokeCertByIssuerAndSerial(issuer, snElement.textContent); - } catch (e) { - // we want to keep trying other elements since missing all items - // is worse than missing one - LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Issuer and Serial" + e); - } - } - return; - } - - let pubKeyHash = blocklistElement.getAttribute("pubKeyHash"); - let subject = blocklistElement.getAttribute("subject"); - - if (pubKeyHash && subject) { - try { - gCertBlocklistService.revokeCertBySubjectAndPubKey(subject, pubKeyHash); - } catch (e) { - LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Subject and PubKey" + e); - } - } - }, - - _handleEmItemNode: function(blocklistElement, result) { - if (!matchesOSABI(blocklistElement)) - return; - - let blockEntry = { - versions: [], - prefs: [], - blockID: null, - attributes: new Map() - // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes - }; - - // Any filter starting with '/' is interpreted as a regex. So if an attribute - // starts with a '/' it must be checked via a regex. - function regExpCheck(attr) { - return attr.startsWith("/") ? parseRegExp(attr) : attr; - } - - for (let filter of EXTENSION_BLOCK_FILTERS) { - let attr = blocklistElement.getAttribute(filter); - if (attr) - blockEntry.attributes.set(filter, regExpCheck(attr)); - } - - var childNodes = blocklistElement.childNodes; - - for (let x = 0; x < childNodes.length; x++) { - var childElement = childNodes.item(x); - if (!(childElement instanceof Ci.nsIDOMElement)) - continue; - if (childElement.localName === "prefs") { - let prefElements = childElement.childNodes; - for (let i = 0; i < prefElements.length; i++) { - let prefElement = prefElements.item(i); - if (!(prefElement instanceof Ci.nsIDOMElement) || - prefElement.localName !== "pref") - continue; - blockEntry.prefs.push(prefElement.textContent); - } - } - else if (childElement.localName === "versionRange") - blockEntry.versions.push(new BlocklistItemData(childElement)); - } - // if only the extension ID is specified block all versions of the - // extension for the current application. - if (blockEntry.versions.length == 0) - blockEntry.versions.push(new BlocklistItemData(null)); - - blockEntry.blockID = blocklistElement.getAttribute("blockID"); - - result.push(blockEntry); - }, - - _handlePluginItemNode: function(blocklistElement, result) { - if (!matchesOSABI(blocklistElement)) - return; - - var matchNodes = blocklistElement.childNodes; - var blockEntry = { - matches: {}, - versions: [], - blockID: null, - infoURL: null, - }; - var hasMatch = false; - for (var x = 0; x < matchNodes.length; ++x) { - var matchElement = matchNodes.item(x); - if (!(matchElement instanceof Ci.nsIDOMElement)) - continue; - if (matchElement.localName == "match") { - var name = matchElement.getAttribute("name"); - var exp = matchElement.getAttribute("exp"); - try { - blockEntry.matches[name] = new RegExp(exp, "m"); - hasMatch = true; - } catch (e) { - // Ignore invalid regular expressions - } - } - if (matchElement.localName == "versionRange") { - blockEntry.versions.push(new BlocklistItemData(matchElement)); - } - else if (matchElement.localName == "infoURL") { - blockEntry.infoURL = matchElement.textContent; - } - } - // Plugin entries require *something* to match to an actual plugin - if (!hasMatch) - return; - // Add a default versionRange if there wasn't one specified - if (blockEntry.versions.length == 0) - blockEntry.versions.push(new BlocklistItemData(null)); - - blockEntry.blockID = blocklistElement.getAttribute("blockID"); - - result.push(blockEntry); - }, - - // - // WINNT 6.0 - // 14 currently only used for Android - // - // 0x8086 - // - // 0x2582 - // 0x2782 - // - // DIRECT3D_10_LAYERS - // BLOCKED_DRIVER_VERSION - // 8.52.322.2202 - // 8.52.322.2202 - // LESS_THAN_OR_EQUAL - // foo - // foo - // foo - // foo - // - _handleGfxBlacklistNode: function (blocklistElement, result) { - const blockEntry = {}; - - // The blockID attribute is always present in the actual data produced on server - // (see https://github.com/mozilla/addons-server/blob/2016.05.05/src/olympia/blocklist/templates/blocklist/blocklist.xml#L74) - // But it is sometimes missing in test fixtures. - if (blocklistElement.hasAttribute("blockID")) { - blockEntry.blockID = blocklistElement.getAttribute("blockID"); - } - - // Trim helper (spaces, tabs, no-break spaces..) - const trim = (s) => (s || '').replace(/(^[\s\uFEFF\xA0]+)|([\s\uFEFF\xA0]+$)/g, ""); - - for (let i = 0; i < blocklistElement.childNodes.length; ++i) { - var matchElement = blocklistElement.childNodes.item(i); - if (!(matchElement instanceof Ci.nsIDOMElement)) - continue; - - let value; - if (matchElement.localName == "devices") { - value = []; - for (let j = 0; j < matchElement.childNodes.length; j++) { - const childElement = matchElement.childNodes.item(j); - const childValue = trim(childElement.textContent); - // Make sure no empty value is added. - if (childValue) { - if (/,/.test(childValue)) { - // Devices can't contain comma. - // (c.f serialization in _notifyObserversBlocklistGFX) - const e = new Error(`Unsupported device name ${childValue}`); - Components.utils.reportError(e); - } - else { - value.push(childValue); - } - } - } - } else if (matchElement.localName == "versionRange") { - value = {minVersion: trim(matchElement.getAttribute("minVersion")) || "0", - maxVersion: trim(matchElement.getAttribute("maxVersion")) || "*"}; - } else { - value = trim(matchElement.textContent); - } - if (value) { - blockEntry[matchElement.localName] = value; - } - } - result.push(blockEntry); - }, - - /* See nsIBlocklistService */ - getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) { - if (AppConstants.platform == "android") { - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - } - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - return this._getPluginBlocklistState(plugin, this._pluginEntries, - appVersion, toolkitVersion); - }, - - /** - * Private helper to get the blocklist entry for a plugin given a set of - * blocklist entries and versions. - * - * @param plugin - * The nsIPluginTag to get the blocklist state for. - * @param pluginEntries - * The plugin blocklist entries to compare against. - * @param appVersion - * The application version to compare to, will use the current - * version if null. - * @param toolkitVersion - * The toolkit version to compare to, will use the current version if - * null. - * @returns {entry: blocklistEntry, version: blocklistEntryVersion}, - * or null if there is no matching entry. - */ - _getPluginBlocklistEntry: function(plugin, pluginEntries, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return null; - - // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't). - if (!appVersion && !gApp.version) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - if (!appVersion) - appVersion = gApp.version; - if (!toolkitVersion) - toolkitVersion = gApp.platformVersion; - - for (var blockEntry of pluginEntries) { - var matchFailed = false; - for (var name in blockEntry.matches) { - if (!(name in plugin) || - typeof(plugin[name]) != "string" || - !blockEntry.matches[name].test(plugin[name])) { - matchFailed = true; - break; - } - } - - if (matchFailed) - continue; - - for (let blockEntryVersion of blockEntry.versions) { - if (blockEntryVersion.includesItem(plugin.version, appVersion, - toolkitVersion)) { - return {entry: blockEntry, version: blockEntryVersion}; - } - } - } - - return null; - }, - - /** - * Private version of getPluginBlocklistState that allows the caller to pass in - * the plugin blocklist entries. - * - * @param plugin - * The nsIPluginTag to get the blocklist state for. - * @param pluginEntries - * The plugin blocklist entries to compare against. - * @param appVersion - * The application version to compare to, will use the current - * version if null. - * @param toolkitVersion - * The toolkit version to compare to, will use the current version if - * null. - * @returns The blocklist state for the item, one of the STATE constants as - * defined in nsIBlocklistService. - */ - _getPluginBlocklistState: function(plugin, pluginEntries, appVersion, toolkitVersion) { - - let r = this._getPluginBlocklistEntry(plugin, pluginEntries, - appVersion, toolkitVersion); - if (!r) { - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - } - - let {entry: blockEntry, version: blockEntryVersion} = r; - - if (blockEntryVersion.severity >= gBlocklistLevel) - return Ci.nsIBlocklistService.STATE_BLOCKED; - if (blockEntryVersion.severity == SEVERITY_OUTDATED) { - let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus; - if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE) - return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE; - if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE) - return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE; - return Ci.nsIBlocklistService.STATE_OUTDATED; - } - return Ci.nsIBlocklistService.STATE_SOFTBLOCKED; - }, - - /* See nsIBlocklistService */ - getPluginBlocklistURL: function(plugin) { - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - - let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries); - if (!r) { - return null; - } - let {entry: blockEntry, version: blockEntryVersion} = r; - if (!blockEntry.blockID) { - return null; - } - - return this._createBlocklistURL(blockEntry.blockID); - }, - - /* See nsIBlocklistService */ - getPluginInfoURL: function(plugin) { - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - - let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries); - if (!r) { - return null; - } - let {entry: blockEntry, version: blockEntryVersion} = r; - if (!blockEntry.blockID) { - return null; - } - - return blockEntry.infoURL; - }, - - _notifyObserversBlocklistGFX: function () { - // Notify `GfxInfoBase`, by passing a string serialization. - // This way we avoid spreading XML structure logics there. - const payload = this._gfxEntries.map((r) => { - return Object.keys(r).sort().filter((k) => !/id|last_modified/.test(k)).map((key) => { - let value = r[key]; - if (Array.isArray(value)) { - value = value.join(","); - } else if (value.hasOwnProperty("minVersion")) { - // When XML is parsed, both minVersion and maxVersion are set. - value = `${value.minVersion},${value.maxVersion}`; - } - return `${key}:${value}`; - }).join("\t"); - }).join("\n"); - Services.obs.notifyObservers(null, "blocklist-data-gfxItems", payload); - }, - - _notifyObserversBlocklistUpdated: function() { - Services.obs.notifyObservers(this, "blocklist-updated", ""); - Services.ppmm.broadcastAsyncMessage("Blocklist:blocklistInvalidated", {}); - }, - - _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) { - var addonList = []; - - // A helper function that reverts the prefs passed to default values. - function resetPrefs(prefs) { - for (let pref of prefs) - gPref.clearUserPref(pref); - } - const types = ["extension", "theme", "locale", "dictionary", "service"]; - AddonManager.getAddonsByTypes(types, addons => { - for (let addon of addons) { - let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED; - if (oldAddonEntries) - oldState = this._getAddonBlocklistState(addon, oldAddonEntries); - let state = this.getAddonBlocklistState(addon); - - LOG("Blocklist state for " + addon.id + " changed from " + - oldState + " to " + state); - - // We don't want to re-warn about add-ons - if (state == oldState) - continue; - - if (state === Ci.nsIBlocklistService.STATE_BLOCKED) { - // It's a hard block. We must reset certain preferences. - let prefs = this._getAddonPrefs(addon); - resetPrefs(prefs); - } - - // Ensure that softDisabled is false if the add-on is not soft blocked - if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - addon.softDisabled = false; - - // Don't warn about add-ons becoming unblocked. - if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED) - continue; - - // If an add-on has dropped from hard to soft blocked just mark it as - // soft disabled and don't warn about it. - if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && - oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { - addon.softDisabled = true; - continue; - } - - // If the add-on is already disabled for some reason then don't warn - // about it - if (!addon.isActive) { - // But mark it as softblocked if necessary. Note that we avoid setting - // softDisabled at the same time as userDisabled to make it clear - // which was the original cause of the add-on becoming disabled in a - // way that the user can change. - if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && !addon.userDisabled) - addon.softDisabled = true; - continue; - } - - addonList.push({ - name: addon.name, - version: addon.version, - icon: addon.iconURL, - disable: false, - blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, - item: addon, - url: this.getAddonBlocklistURL(addon), - }); - } - - AddonManagerPrivate.updateAddonAppDisabledStates(); - - var phs = Cc["@mozilla.org/plugin/host;1"]. - getService(Ci.nsIPluginHost); - var plugins = phs.getPluginTags(); - - for (let plugin of plugins) { - let oldState = -1; - if (oldPluginEntries) - oldState = this._getPluginBlocklistState(plugin, oldPluginEntries); - let state = this.getPluginBlocklistState(plugin); - LOG("Blocklist state for " + plugin.name + " changed from " + - oldState + " to " + state); - // We don't want to re-warn about items - if (state == oldState) - continue; - - if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { - if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; - } - else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { - if (state != Ci.nsIBlocklistService.STATE_OUTDATED && - state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE && - state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { - addonList.push({ - name: plugin.name, - version: plugin.version, - icon: "chrome://mozapps/skin/plugins/pluginGeneric.png", - disable: false, - blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, - item: plugin, - url: this.getPluginBlocklistURL(plugin), - }); - } - } - } - - if (addonList.length == 0) { - this._notifyObserversBlocklistUpdated(); - return; - } - - if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) { - try { - let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"] - .getService(Ci.nsIBlocklistPrompt); - blockedPrompter.prompt(addonList); - } catch (e) { - LOG(e); - } - this._notifyObserversBlocklistUpdated(); - return; - } - - var args = { - restart: false, - list: addonList - }; - // This lets the dialog get the raw js object - args.wrappedJSObject = args; - - /* - Some tests run without UI, so the async code listens to a message - that can be sent programatically - */ - let applyBlocklistChanges = () => { - for (let addon of addonList) { - if (!addon.disable) - continue; - - if (addon.item instanceof Ci.nsIPluginTag) - addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED; - else { - // This add-on is softblocked. - addon.item.softDisabled = true; - // We must revert certain prefs. - let prefs = this._getAddonPrefs(addon.item); - resetPrefs(prefs); - } - } - - if (args.restart) - restartApp(); - - this._notifyObserversBlocklistUpdated(); - Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed"); - } - - Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false); - - if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) { - applyBlocklistChanges(); - return; - } - - function blocklistUnloadHandler(event) { - if (event.target.location == URI_BLOCKLIST_DIALOG) { - applyBlocklistChanges(); - blocklistWindow.removeEventListener("unload", blocklistUnloadHandler); - } - } - - let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "", - "chrome,centerscreen,dialog,titlebar", args); - if (blocklistWindow) - blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false); - }); - }, - - classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsIBlocklistService, - Ci.nsITimerCallback]), -}; - -/** - * Helper for constructing a blocklist. - */ -function BlocklistItemData(versionRangeElement) { - var versionRange = this.getBlocklistVersionRange(versionRangeElement); - this.minVersion = versionRange.minVersion; - this.maxVersion = versionRange.maxVersion; - if (versionRangeElement && versionRangeElement.hasAttribute("severity")) - this.severity = versionRangeElement.getAttribute("severity"); - else - this.severity = DEFAULT_SEVERITY; - if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) { - this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus"); - } else { - this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE; - } - this.targetApps = { }; - var found = false; - - if (versionRangeElement) { - for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { - var targetAppElement = versionRangeElement.childNodes.item(i); - if (!(targetAppElement instanceof Ci.nsIDOMElement) || - targetAppElement.localName != "targetApplication") - continue; - found = true; - // default to the current application if id is not provided. - var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID; - this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement); - } - } - // Default to all versions of the current application when no targetApplication - // elements were found - if (!found) - this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); -} - -BlocklistItemData.prototype = { - /** - * Tests if a version of an item is included in the version range and target - * application information represented by this BlocklistItemData using the - * provided application and toolkit versions. - * @param version - * The version of the item being tested. - * @param appVersion - * The application version to test with. - * @param toolkitVersion - * The toolkit version to test with. - * @returns True if the version range covers the item version and application - * or toolkit version. - */ - includesItem: function(version, appVersion, toolkitVersion) { - // Some platforms have no version for plugins, these don't match if there - // was a min/maxVersion provided - if (!version && (this.minVersion || this.maxVersion)) - return false; - - // Check if the item version matches - if (!this.matchesRange(version, this.minVersion, this.maxVersion)) - return false; - - // Check if the application version matches - if (this.matchesTargetRange(gApp.ID, appVersion)) - return true; - - // Check if the toolkit version matches - return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion); - }, - - /** - * Checks if a version is higher than or equal to the minVersion (if provided) - * and lower than or equal to the maxVersion (if provided). - * @param version - * The version to test. - * @param minVersion - * The minimum version. If null it is assumed that version is always - * larger. - * @param maxVersion - * The maximum version. If null it is assumed that version is always - * smaller. - */ - matchesRange: function(version, minVersion, maxVersion) { - if (minVersion && gVersionChecker.compare(version, minVersion) < 0) - return false; - if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0) - return false; - return true; - }, - - /** - * Tests if there is a matching range for the given target application id and - * version. - * @param appID - * The application ID to test for, may be for an application or toolkit - * @param appVersion - * The version of the application to test for. - * @returns True if this version range covers the application version given. - */ - matchesTargetRange: function(appID, appVersion) { - var blTargetApp = this.targetApps[appID]; - if (!blTargetApp) - return false; - - for (let app of blTargetApp) { - if (this.matchesRange(appVersion, app.minVersion, app.maxVersion)) - return true; - } - - return false; - }, - - /** - * Retrieves a version range (e.g. minVersion and maxVersion) for a - * blocklist item's targetApplication element. - * @param targetAppElement - * A targetApplication blocklist element. - * @returns An array of JS objects with the following properties: - * "minVersion" The minimum version in a version range (default = null). - * "maxVersion" The maximum version in a version range (default = null). - */ - getBlocklistAppVersions: function(targetAppElement) { - var appVersions = [ ]; - - if (targetAppElement) { - for (var i = 0; i < targetAppElement.childNodes.length; ++i) { - var versionRangeElement = targetAppElement.childNodes.item(i); - if (!(versionRangeElement instanceof Ci.nsIDOMElement) || - versionRangeElement.localName != "versionRange") - continue; - appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); - } - } - // return minVersion = null and maxVersion = null if no specific versionRange - // elements were found - if (appVersions.length == 0) - appVersions.push(this.getBlocklistVersionRange(null)); - return appVersions; - }, - - /** - * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist - * versionRange element. - * @param versionRangeElement - * The versionRange blocklist element. - * @returns A JS object with the following properties: - * "minVersion" The minimum version in a version range (default = null). - * "maxVersion" The maximum version in a version range (default = null). - */ - getBlocklistVersionRange: function(versionRangeElement) { - var minVersion = null; - var maxVersion = null; - if (!versionRangeElement) - return { minVersion: minVersion, maxVersion: maxVersion }; - - if (versionRangeElement.hasAttribute("minVersion")) - minVersion = versionRangeElement.getAttribute("minVersion"); - if (versionRangeElement.hasAttribute("maxVersion")) - maxVersion = versionRangeElement.getAttribute("maxVersion"); - - return { minVersion: minVersion, maxVersion: maxVersion }; - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]); diff --git a/toolkit/mozapps/webextensions/nsBlocklistServiceContent.js b/toolkit/mozapps/webextensions/nsBlocklistServiceContent.js deleted file mode 100644 index 1752924b5..000000000 --- a/toolkit/mozapps/webextensions/nsBlocklistServiceContent.js +++ /dev/null @@ -1,113 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); - -const kMissingAPIMessage = "Unsupported blocklist call in the child process." - -/* - * A lightweight blocklist proxy for the content process that traps plugin - * related blocklist checks and forwards them to the parent. This interface is - * primarily designed to insure overlays work.. it does not control plugin - * or addon loading. - */ - -function Blocklist() { - this.init(); -} - -Blocklist.prototype = { - classID: Components.ID("{e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d}"), - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsIBlocklistService]), - - init: function() { - Services.cpmm.addMessageListener("Blocklist:blocklistInvalidated", this); - Services.obs.addObserver(this, "xpcom-shutdown", false); - }, - - uninit: function() { - Services.cpmm.removeMessageListener("Blocklist:blocklistInvalidated", this); - Services.obs.removeObserver(this, "xpcom-shutdown", false); - }, - - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case "xpcom-shutdown": - this.uninit(); - break; - } - }, - - // Message manager message handlers - receiveMessage: function(aMsg) { - switch (aMsg.name) { - case "Blocklist:blocklistInvalidated": - Services.obs.notifyObservers(null, "blocklist-updated", null); - Services.cpmm.sendAsyncMessage("Blocklist:content-blocklist-updated"); - break; - default: - throw new Error("Unknown blocklist message received from content: " + aMsg.name); - } - }, - - /* - * A helper that queries key data from a plugin or addon object - * and generates a simple data wrapper suitable for ipc. We hand - * these directly to the nsBlockListService in the parent which - * doesn't query for much.. allowing us to get away with this. - */ - flattenObject: function(aTag) { - // Based on debugging the nsBlocklistService, these are the props the - // parent side will check on our objects. - let props = ["name", "description", "filename", "version"]; - let dataWrapper = {}; - for (let prop of props) { - dataWrapper[prop] = aTag[prop]; - } - return dataWrapper; - }, - - // We support the addon methods here for completeness, but content currently - // only calls getPluginBlocklistState. - - isAddonBlocklisted: function(aAddon, aAppVersion, aToolkitVersion) { - return true; - }, - - getAddonBlocklistState: function(aAddon, aAppVersion, aToolkitVersion) { - return Components.interfaces.nsIBlocklistService.STATE_BLOCKED; - }, - - // There are a few callers in layout that rely on this. - getPluginBlocklistState: function(aPluginTag, aAppVersion, aToolkitVersion) { - return Services.cpmm.sendSyncMessage("Blocklist:getPluginBlocklistState", { - addonData: this.flattenObject(aPluginTag), - appVersion: aAppVersion, - toolkitVersion: aToolkitVersion - })[0]; - }, - - getAddonBlocklistURL: function(aAddon, aAppVersion, aToolkitVersion) { - throw new Error(kMissingAPIMessage); - }, - - getPluginBlocklistURL: function(aPluginTag) { - throw new Error(kMissingAPIMessage); - }, - - getPluginInfoURL: function(aPluginTag) { - throw new Error(kMissingAPIMessage); - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]); -- cgit v1.2.3 From ca6136ffb67b9f4119cc65d1fb0ce388d4fece7c Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Tue, 15 May 2018 14:00:38 +0200 Subject: Drop invisible characters from downloads filename. --- toolkit/content/contentAreaUtils.js | 1 + 1 file changed, 1 insertion(+) (limited to 'toolkit') diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js index 2b7af30de..736fb7dfc 100644 --- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -1108,6 +1108,7 @@ function getDefaultFileName(aDefaultFileName, aURI, aDocument, function validateFileName(aFileName) { + aFileName = aFileName.replace(/[\u200e\u200f\u202a-\u202e]/g, ""); var re = /[\/]+/g; if (navigator.appVersion.indexOf("Windows") != -1) { re = /[\\\/\|]+/g; -- cgit v1.2.3 From 3ac23df787f21eb51e39aaa3c34756b8c32a75cb Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 15 May 2018 21:04:46 +0200 Subject: [PALEMOON] Fix: The settings icon in the notification is missing (copy some files from webextensions) Issue #121 Pull #273 --- .../themes/shared/extensions/alerticon-error.svg | 6 ++++++ .../shared/extensions/alerticon-info-negative.svg | 6 ++++++ .../shared/extensions/alerticon-info-positive.svg | 6 ++++++ .../themes/shared/extensions/alerticon-warning.svg | 6 ++++++ .../themes/shared/extensions/extensionGeneric.svg | 12 ++++++++++++ toolkit/themes/shared/extensions/utilities.svg | 21 +++++++++++++-------- 6 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 toolkit/themes/shared/extensions/alerticon-error.svg create mode 100644 toolkit/themes/shared/extensions/alerticon-info-negative.svg create mode 100644 toolkit/themes/shared/extensions/alerticon-info-positive.svg create mode 100644 toolkit/themes/shared/extensions/alerticon-warning.svg create mode 100644 toolkit/themes/shared/extensions/extensionGeneric.svg (limited to 'toolkit') diff --git a/toolkit/themes/shared/extensions/alerticon-error.svg b/toolkit/themes/shared/extensions/alerticon-error.svg new file mode 100644 index 000000000..cb883e16e --- /dev/null +++ b/toolkit/themes/shared/extensions/alerticon-error.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/extensions/alerticon-info-negative.svg b/toolkit/themes/shared/extensions/alerticon-info-negative.svg new file mode 100644 index 000000000..733f8571a --- /dev/null +++ b/toolkit/themes/shared/extensions/alerticon-info-negative.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/extensions/alerticon-info-positive.svg b/toolkit/themes/shared/extensions/alerticon-info-positive.svg new file mode 100644 index 000000000..031190bce --- /dev/null +++ b/toolkit/themes/shared/extensions/alerticon-info-positive.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/extensions/alerticon-warning.svg b/toolkit/themes/shared/extensions/alerticon-warning.svg new file mode 100644 index 000000000..2b403220e --- /dev/null +++ b/toolkit/themes/shared/extensions/alerticon-warning.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/extensions/extensionGeneric.svg b/toolkit/themes/shared/extensions/extensionGeneric.svg new file mode 100644 index 000000000..28c2f7ba3 --- /dev/null +++ b/toolkit/themes/shared/extensions/extensionGeneric.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/toolkit/themes/shared/extensions/utilities.svg b/toolkit/themes/shared/extensions/utilities.svg index fd911001b..8bf24458c 100644 --- a/toolkit/themes/shared/extensions/utilities.svg +++ b/toolkit/themes/shared/extensions/utilities.svg @@ -1,11 +1,8 @@ - + + - + + + -- cgit v1.2.3 From b70d884598e1e14b99190e1e5c349553ff59849b Mon Sep 17 00:00:00 2001 From: Ascrod <32915892+Ascrod@users.noreply.github.com> Date: Mon, 7 May 2018 19:45:46 -0400 Subject: Initial updates for Reader View. --- toolkit/components/narrate/NarrateControls.jsm | 134 ++-- toolkit/components/narrate/Narrator.jsm | 78 +- toolkit/components/narrate/VoiceSelect.jsm | 23 +- .../components/printing/content/simplifyMode.css | 2 +- toolkit/components/reader/AboutReader.jsm | 314 ++++---- toolkit/components/reader/JSDOMParser.js | 46 +- toolkit/components/reader/Readability.js | 853 +++++++++------------ toolkit/components/reader/ReaderMode.jsm | 312 +++++--- toolkit/components/reader/ReaderWorker.js | 7 +- toolkit/components/reader/content/aboutReader.html | 73 +- toolkit/components/reader/content/aboutReader.js | 2 +- toolkit/content/browser-content.js | 6 - .../en-US/chrome/global/aboutReader.properties | 16 + toolkit/themes/shared/aboutReader.css | 626 ++++++++++++++- toolkit/themes/shared/aboutReaderContent.css | 177 ----- toolkit/themes/shared/aboutReaderControls.css | 388 ---------- toolkit/themes/shared/jar.inc.mn | 5 +- toolkit/themes/shared/narrate.css | 195 ++++- toolkit/themes/shared/narrateControls.css | 185 ----- .../shared/reader/RM-Content-Width-Minus-42x16.svg | 5 +- .../shared/reader/RM-Content-Width-Plus-44x16.svg | 5 +- .../shared/reader/RM-Line-Height-Minus-38x14.svg | 5 +- .../shared/reader/RM-Line-Height-Plus-38x24.svg | 5 +- .../shared/reader/RM-Type-Controls-Arrow.svg | 16 +- 24 files changed, 1742 insertions(+), 1736 deletions(-) delete mode 100644 toolkit/themes/shared/aboutReaderContent.css delete mode 100644 toolkit/themes/shared/aboutReaderControls.css delete mode 100644 toolkit/themes/shared/narrateControls.css (limited to 'toolkit') diff --git a/toolkit/components/narrate/NarrateControls.jsm b/toolkit/components/narrate/NarrateControls.jsm index 7d8794b18..828292cc2 100644 --- a/toolkit/components/narrate/NarrateControls.jsm +++ b/toolkit/components/narrate/NarrateControls.jsm @@ -16,9 +16,10 @@ this.EXPORTED_SYMBOLS = ["NarrateControls"]; var gStrings = Services.strings.createBundle("chrome://global/locale/narrate.properties"); -function NarrateControls(mm, win) { +function NarrateControls(mm, win, languagePromise) { this._mm = mm; this._winRef = Cu.getWeakReference(win); + this._languagePromise = languagePromise; win.addEventListener("unload", this); @@ -37,16 +38,12 @@ function NarrateControls(mm, win) { } let dropdown = win.document.createElement("ul"); - dropdown.className = "dropdown"; - dropdown.id = "narrate-dropdown"; + dropdown.className = "dropdown narrate-dropdown"; // We need inline svg here for the animation to work (bug 908634 & 1190881). - // The style animation can't be scoped (bug 830056). + // eslint-disable-next-line no-unsanitized/property dropdown.innerHTML = - localize` -
  • -
  • `; - this.narrator = new Narrator(win); + this.narrator = new Narrator(win, languagePromise); let branch = Services.prefs.getBranch("narrate."); let selectLabel = gStrings.GetStringFromName("selectvoicelabel"); this.voiceSelect = new VoiceSelect(win, selectLabel); this.voiceSelect.element.addEventListener("change", this); - this.voiceSelect.element.id = "voice-select"; + this.voiceSelect.element.classList.add("voice-select"); win.speechSynthesis.addEventListener("voiceschanged", this); - dropdown.querySelector("#narrate-voices").appendChild( + dropdown.querySelector(".narrate-voices").appendChild( this.voiceSelect.element); dropdown.addEventListener("click", this, true); - let rateRange = dropdown.querySelector("#narrate-rate > input"); + let rateRange = dropdown.querySelector(".narrate-rate > input"); rateRange.addEventListener("change", this); // The rate is stored as an integer. @@ -131,15 +128,15 @@ function NarrateControls(mm, win) { this._setupVoices(); - let tb = win.document.getElementById("reader-toolbar"); + let tb = win.document.querySelector(".reader-toolbar"); tb.appendChild(dropdown); } NarrateControls.prototype = { - handleEvent: function(evt) { + handleEvent(evt) { switch (evt.type) { case "change": - if (evt.target.id == "narrate-rate-input") { + if (evt.target.classList.contains("narrate-rate-input")) { this._onRateInput(evt); } else { this._onVoiceChange(); @@ -162,8 +159,8 @@ NarrateControls.prototype = { /** * Returns true if synth voices are available. */ - _setupVoices: function() { - return this.narrator.languagePromise.then(language => { + _setupVoices() { + return this._languagePromise.then(language => { this.voiceSelect.clear(); let win = this._win; let voicePrefs = this._getVoicePref(); @@ -190,7 +187,7 @@ NarrateControls.prototype = { this.voiceSelect.addOptions(options); } - let narrateToggle = win.document.getElementById("narrate-toggle"); + let narrateToggle = win.document.querySelector(".narrate-toggle"); let histogram = Services.telemetry.getKeyedHistogramById( "NARRATE_CONTENT_BY_LANGUAGE_2"); let initial = !this._voicesInitialized; @@ -210,7 +207,7 @@ NarrateControls.prototype = { }); }, - _getVoicePref: function() { + _getVoicePref() { let voicePref = Services.prefs.getCharPref("narrate.voice"); try { return JSON.parse(voicePref); @@ -219,15 +216,15 @@ NarrateControls.prototype = { } }, - _onRateInput: function(evt) { + _onRateInput(evt) { AsyncPrefs.set("narrate.rate", parseInt(evt.target.value, 10)); this.narrator.setRate(this._convertRate(evt.target.value)); }, - _onVoiceChange: function() { + _onVoiceChange() { let voice = this.voice; this.narrator.setVoice(voice); - this.narrator.languagePromise.then(language => { + this._languagePromise.then(language => { if (language) { let voicePref = this._getVoicePref(); voicePref[language || "default"] = voice; @@ -236,42 +233,39 @@ NarrateControls.prototype = { }); }, - _onButtonClick: function(evt) { - switch (evt.target.id) { - case "narrate-skip-previous": - this.narrator.skipPrevious(); - break; - case "narrate-skip-next": - this.narrator.skipNext(); - break; - case "narrate-start-stop": - if (this.narrator.speaking) { - this.narrator.stop(); - } else { - this._updateSpeechControls(true); - let options = { rate: this.rate, voice: this.voice }; - this.narrator.start(options).then(() => { - this._updateSpeechControls(false); - }, err => { - Cu.reportError(`Narrate failed: ${err}.`); - this._updateSpeechControls(false); - }); - } - break; + _onButtonClick(evt) { + let classList = evt.target.classList; + if (classList.contains("narrate-skip-previous")) { + this.narrator.skipPrevious(); + } else if (classList.contains("narrate-skip-next")) { + this.narrator.skipNext(); + } else if (classList.contains("narrate-start-stop")) { + if (this.narrator.speaking) { + this.narrator.stop(); + } else { + this._updateSpeechControls(true); + let options = { rate: this.rate, voice: this.voice }; + this.narrator.start(options).then(() => { + this._updateSpeechControls(false); + }, err => { + Cu.reportError(`Narrate failed: ${err}.`); + this._updateSpeechControls(false); + }); + } } }, - _updateSpeechControls: function(speaking) { - let dropdown = this._doc.getElementById("narrate-dropdown"); + _updateSpeechControls(speaking) { + let dropdown = this._doc.querySelector(".narrate-dropdown"); dropdown.classList.toggle("keep-open", speaking); dropdown.classList.toggle("speaking", speaking); - let startStopButton = this._doc.getElementById("narrate-start-stop"); + let startStopButton = this._doc.querySelector(".narrate-start-stop"); startStopButton.title = gStrings.GetStringFromName(speaking ? "stop" : "start"); - this._doc.getElementById("narrate-skip-previous").disabled = !speaking; - this._doc.getElementById("narrate-skip-next").disabled = !speaking; + this._doc.querySelector(".narrate-skip-previous").disabled = !speaking; + this._doc.querySelector(".narrate-skip-next").disabled = !speaking; if (speaking) { TelemetryStopwatch.start("NARRATE_CONTENT_SPEAKTIME_MS", this); @@ -280,7 +274,7 @@ NarrateControls.prototype = { } }, - _createVoiceLabel: function(voice) { + _createVoiceLabel(voice) { // This is a highly imperfect method of making human-readable labels // for system voices. Because each platform has a different naming scheme // for voices, we use a different method for each platform. @@ -303,7 +297,7 @@ NarrateControls.prototype = { } }, - _getLanguageName: function(lang) { + _getLanguageName(lang) { if (!this._langStrings) { this._langStrings = Services.strings.createBundle( "chrome://global/locale/languageNames.properties "); @@ -317,7 +311,7 @@ NarrateControls.prototype = { } }, - _convertRate: function(rate) { + _convertRate(rate) { // We need to convert a relative percentage value to a fraction rate value. // eg. -100 is half the speed, 100 is twice the speed in percentage, // 0.5 is half the speed and 2 is twice the speed in fractions. @@ -334,7 +328,7 @@ NarrateControls.prototype = { get rate() { return this._convertRate( - this._doc.getElementById("narrate-rate-input").value); + this._doc.querySelector(".narrate-rate-input").value); }, get voice() { diff --git a/toolkit/components/narrate/Narrator.jsm b/toolkit/components/narrate/Narrator.jsm index ade06510e..ac0b2e040 100644 --- a/toolkit/components/narrate/Narrator.jsm +++ b/toolkit/components/narrate/Narrator.jsm @@ -8,8 +8,6 @@ const { interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector", - "resource:///modules/translation/LanguageDetector.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); @@ -24,29 +22,13 @@ const kTextStylesRules = ["font-family", "font-kerning", "font-size", "line-height", "letter-spacing", "text-orientation", "text-transform", "word-spacing"]; -function Narrator(win) { +function Narrator(win, languagePromise) { this._winRef = Cu.getWeakReference(win); + this._languagePromise = languagePromise; this._inTest = Services.prefs.getBoolPref("narrate.test"); this._speechOptions = {}; this._startTime = 0; this._stopped = false; - - this.languagePromise = new Promise(resolve => { - let detect = () => { - win.document.removeEventListener("AboutReaderContentReady", detect); - let sampleText = this._doc.getElementById( - "moz-reader-content").textContent.substring(0, 60 * 1024); - LanguageDetector.detectLanguage(sampleText).then(result => { - resolve(result.confident ? result.language : null); - }); - }; - - if (win.document.body.classList.contains("loaded")) { - detect(); - } else { - win.document.addEventListener("AboutReaderContentReady", detect); - } - }); } Narrator.prototype = { @@ -71,7 +53,7 @@ Narrator.prototype = { // For example, paragraphs. But nested anchors and other elements // are not interesting since their text already appears in their // parent's textContent. - acceptNode: function(node) { + acceptNode(node) { if (this._matches.has(node.parentNode)) { // Reject sub-trees of accepted nodes. return nf.FILTER_REJECT; @@ -107,7 +89,7 @@ Narrator.prototype = { // are no other strong references, and it will be GC'ed. Instead, // we rely on the window's lifetime and use it as a weak reference. this._treeWalkerRef.set(this._win, - this._doc.createTreeWalker(this._doc.getElementById("container"), + this._doc.createTreeWalker(this._doc.querySelector(".container"), nf.SHOW_ELEMENT, filter, false)); } @@ -124,7 +106,7 @@ Narrator.prototype = { this._win.speechSynthesis.pending; }, - _getVoice: function(voiceURI) { + _getVoice(voiceURI) { if (!this._voiceMap || !this._voiceMap.has(voiceURI)) { this._voiceMap = new Map( this._win.speechSynthesis.getVoices().map(v => [v.voiceURI, v])); @@ -133,7 +115,7 @@ Narrator.prototype = { return this._voiceMap.get(voiceURI); }, - _isParagraphInView: function(paragraph) { + _isParagraphInView(paragraph) { if (!paragraph) { return false; } @@ -142,13 +124,13 @@ Narrator.prototype = { return bb.top >= 0 && bb.top < this._win.innerHeight; }, - _sendTestEvent: function(eventType, detail) { + _sendTestEvent(eventType, detail) { let win = this._win; win.dispatchEvent(new win.CustomEvent(eventType, { detail: Cu.cloneInto(detail, win.document) })); }, - _speakInner: function() { + _speakInner() { this._win.speechSynthesis.cancel(); let tw = this._treeWalker; let paragraph = tw.currentNode; @@ -238,18 +220,12 @@ Narrator.prototype = { return; } - // Match non-whitespace. This isn't perfect, but the most universal - // solution for now. - let reWordBoundary = /\S+/g; - // Match the first word from the boundary event offset. - reWordBoundary.lastIndex = e.charIndex; - let firstIndex = reWordBoundary.exec(paragraph.textContent); - if (firstIndex) { - highlighter.highlight(firstIndex.index, reWordBoundary.lastIndex); + if (e.charLength) { + highlighter.highlight(e.charIndex, e.charLength); if (this._inTest) { this._sendTestEvent("wordhighlight", { - start: firstIndex.index, - end: reWordBoundary.lastIndex + start: e.charIndex, + end: e.charIndex + e.charLength }); } } @@ -259,14 +235,14 @@ Narrator.prototype = { }); }, - start: function(speechOptions) { + start(speechOptions) { this._speechOptions = { rate: speechOptions.rate, voice: this._getVoice(speechOptions.voice) }; this._stopped = false; - return this.languagePromise.then(language => { + return this._languagePromise.then(language => { if (!this._speechOptions.voice) { this._speechOptions.lang = language; } @@ -288,32 +264,32 @@ Narrator.prototype = { }); }, - stop: function() { + stop() { this._stopped = true; this._win.speechSynthesis.cancel(); }, - skipNext: function() { + skipNext() { this._win.speechSynthesis.cancel(); }, - skipPrevious: function() { + skipPrevious() { this._goBackParagraphs(this._timeIntoParagraph < PREV_THRESHOLD ? 2 : 1); }, - setRate: function(rate) { + setRate(rate) { this._speechOptions.rate = rate; /* repeat current paragraph */ this._goBackParagraphs(1); }, - setVoice: function(voice) { + setVoice(voice) { this._speechOptions.voice = this._getVoice(voice); /* repeat current paragraph */ this._goBackParagraphs(1); }, - _goBackParagraphs: function(count) { + _goBackParagraphs(count) { let tw = this._treeWalker; for (let i = 0; i < count; i++) { if (!tw.previousNode()) { @@ -338,13 +314,13 @@ Highlighter.prototype = { * Highlight the range within offsets relative to the container. * * @param {Number} startOffset the start offset - * @param {Number} endOffset the end offset + * @param {Number} length the length in characters of the range */ - highlight: function(startOffset, endOffset) { + highlight(startOffset, length) { let containerRect = this.container.getBoundingClientRect(); - let range = this._getRange(startOffset, endOffset); + let range = this._getRange(startOffset, startOffset + length); let rangeRects = range.getClientRects(); - let win = this.container.ownerDocument.defaultView; + let win = this.container.ownerGlobal; let computedStyle = win.getComputedStyle(range.endContainer.parentNode); let nodes = this._getFreshHighlightNodes(rangeRects.length); @@ -386,7 +362,7 @@ Highlighter.prototype = { /** * Releases reference to container and removes all highlight nodes. */ - remove: function() { + remove() { for (let node of this._nodes) { node.remove(); } @@ -400,7 +376,7 @@ Highlighter.prototype = { * * @param {Number} count number of nodes needed */ - _getFreshHighlightNodes: function(count) { + _getFreshHighlightNodes(count) { let doc = this.container.ownerDocument; let nodes = Array.from(this._nodes); @@ -427,7 +403,7 @@ Highlighter.prototype = { * @param {Number} startOffset the start offset * @param {Number} endOffset the end offset */ - _getRange: function(startOffset, endOffset) { + _getRange(startOffset, endOffset) { let doc = this.container.ownerDocument; let i = 0; let treeWalker = doc.createTreeWalker( diff --git a/toolkit/components/narrate/VoiceSelect.jsm b/toolkit/components/narrate/VoiceSelect.jsm index b283a06b3..861a21c97 100644 --- a/toolkit/components/narrate/VoiceSelect.jsm +++ b/toolkit/components/narrate/VoiceSelect.jsm @@ -13,6 +13,7 @@ function VoiceSelect(win, label) { let element = win.document.createElement("div"); element.classList.add("voiceselect"); + // eslint-disable-next-line no-unsanitized/property element.innerHTML = `