Bug 1237198 - Block SWFs on the content blocking list hosted on the Shavar service. r=francois
authorTobias Schneider <schneider@jancona.com>
Tue, 29 Mar 2016 00:37:44 -0700
changeset 290905 82a22f34e0e8f3e4f0ac556551df45cb34e6c6fb
parent 290904 323653e6a3d9aee66a5a4bdc8c10c25f8953b523
child 290906 09472720d1fba2515e13c829909f4ce5739c566d
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrancois
bugs1237198
milestone48.0a1
Bug 1237198 - Block SWFs on the content blocking list hosted on the Shavar service. r=francois
browser/components/safebrowsing/content/test/head.js
browser/modules/PluginContent.jsm
dom/base/nsObjectLoadingContent.cpp
js/src/tests/user.js
layout/tools/reftest/reftest-preferences.js
modules/libpref/init/all.js
testing/marionette/client/marionette_driver/geckoinstance.py
testing/talos/talos/config.py
toolkit/components/telemetry/Histograms.json
toolkit/components/url-classifier/SafeBrowsing.jsm
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.h
toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
toolkit/components/url-classifier/tests/unit/test_dbservice.js
toolkit/components/url-classifier/tests/unit/test_streamupdater.js
xpcom/base/ErrorList.h
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -48,10 +48,11 @@ function promiseTabLoadEvent(tab, url, e
     tab.linkedBrowser.loadURI(url);
   }
   return deferred.promise;
 }
 
 Services.prefs.setCharPref("urlclassifier.forbiddenTable", "test-forbid-simple");
 Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
 Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
+Services.prefs.setCharPref("urlclassifier.blockedTable", "test-block-simple");
 Services.prefs.setBoolPref("browser.safebrowsing.forbiddenURIs.enabled", true);
 SafeBrowsing.init();
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -417,18 +417,23 @@ PluginContent.prototype = {
 
       case "PluginDisabled":
         let manageLink = this.getPluginUI(plugin, "managePluginsLink");
         this.addLinkClickCallback(manageLink, "forwardCallback", "managePlugins");
         shouldShowNotification = true;
         break;
 
       case "PluginInstantiated":
-        Services.telemetry.getKeyedHistogramById('PLUGIN_ACTIVATION_COUNT').add(this._getPluginInfo(plugin).pluginTag.niceName);
+        let key = this._getPluginInfo(plugin).pluginTag.niceName;
+        Services.telemetry.getKeyedHistogramById('PLUGIN_ACTIVATION_COUNT').add(key);
         shouldShowNotification = true;
+        let pluginRect = plugin.getBoundingClientRect();
+        if (pluginRect.width <= 5 && pluginRect.height <= 5) {
+          Services.telemetry.getKeyedHistogramById('PLUGIN_TINY_CONTENT').add(key);
+        }
         break;
     }
 
     if (this._getPluginInfo(plugin).mimetype === "application/x-shockwave-flash") {
       this._recordFlashPluginTelemetry(eventType, plugin);
     }
 
     // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -1148,16 +1148,30 @@ nsObjectLoadingContent::OnStopRequest(ns
   // We make a note of this object node by including it in a dedicated
   // array of blocked tracking nodes under its parent document.
   if (aStatusCode == NS_ERROR_TRACKING_URI) {
     nsCOMPtr<nsIContent> thisNode =
       do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
     if (thisNode && thisNode->IsInComposedDoc()) {
       thisNode->GetComposedDoc()->AddBlockedTrackingNode(thisNode);
     }
+  } else if (aStatusCode == NS_ERROR_BLOCKED_URI) {
+    // Logging is temporarily disabled until after experiment phase.
+    //
+    // nsAutoCString uri;
+    // mURI->GetSpec(uri);
+    // nsCOMPtr<nsIConsoleService> console(
+    //   do_GetService("@mozilla.org/consoleservice;1"));
+    // if (console) {
+    //   nsString message = NS_LITERAL_STRING("Blocking ") +
+    //     NS_ConvertASCIItoUTF16(uri) +
+    //     NS_LITERAL_STRING(" since it was found on an internal Firefox blocklist.");
+    //   console->LogStringMessage(message.get());
+    // }
+    Telemetry::Accumulate(Telemetry::PLUGIN_BLOCKED_FOR_STABILITY, 1);
   }
 
   NS_ENSURE_TRUE(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), NS_ERROR_NOT_AVAILABLE);
 
   if (aRequest != mChannel) {
     return NS_BINDING_ABORTED;
   }
 
--- a/js/src/tests/user.js
+++ b/js/src/tests/user.js
@@ -21,15 +21,16 @@ user_pref("browser.EULA.override", true)
 user_pref("javascript.options.strict", false);
 user_pref("javascript.options.werror", false);
 user_pref("toolkit.startup.max_resumed_crashes", -1);
 user_pref("security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", true);
 user_pref("toolkit.telemetry.enabled", false);
 user_pref("browser.safebrowsing.enabled", false);
 user_pref("browser.safebrowsing.malware.enabled", false);
 user_pref("browser.safebrowsing.forbiddenURIs.enabled", false);
+user_pref("browser.safebrowsing.blockedURIs.enabled", false);
 user_pref("privacy.trackingprotection.enabled", false);
 user_pref("privacy.trackingprotection.pbmode.enabled", false);
 user_pref("browser.snippets.enabled", false);
 user_pref("browser.snippets.syncPromo.enabled", false);
 user_pref("browser.snippets.firstrunHomepage.enabled", false);
 user_pref("general.useragent.updates.enabled", false);
 user_pref("browser.webapps.checkForUpdates", 0);
--- a/layout/tools/reftest/reftest-preferences.js
+++ b/layout/tools/reftest/reftest-preferences.js
@@ -77,16 +77,17 @@ user_pref("security.view-source.reachabl
 // Ensure that telemetry is disabled, so we don't connect to the telemetry
 // server in the middle of the tests.
 user_pref("toolkit.telemetry.enabled", false);
 user_pref("toolkit.telemetry.unified", false);
 // Likewise for safebrowsing.
 user_pref("browser.safebrowsing.enabled", false);
 user_pref("browser.safebrowsing.malware.enabled", false);
 user_pref("browser.safebrowsing.forbiddenURIs.enabled", false);
+user_pref("browser.safebrowsing.blockedURIs.enabled", false);
 // Likewise for tracking protection.
 user_pref("privacy.trackingprotection.enabled", false);
 user_pref("privacy.trackingprotection.pbmode.enabled", false);
 // And for snippets.
 user_pref("browser.snippets.enabled", false);
 user_pref("browser.snippets.syncPromo.enabled", false);
 user_pref("browser.snippets.firstrunHomepage.enabled", false);
 // And for useragent updates.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4939,28 +4939,32 @@ pref("dom.inter-app-communication-api.en
 // Disable mapped array buffer by default.
 pref("dom.mapped_arraybuffer.enabled", false);
 
 // The tables used for Safebrowsing phishing and malware checks.
 pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,test-malware-simple,test-unwanted-simple");
 pref("urlclassifier.phishTable", "goog-phish-shavar,test-phish-simple");
 pref("urlclassifier.downloadBlockTable", "");
 pref("urlclassifier.downloadAllowTable", "");
-pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-forbid-simple,goog-downloadwhite-digest256,mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-forbid-simple,goog-downloadwhite-digest256,mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256,test-block-simple,mozplugin-block-digest256,mozplugin2-block-digest256");
 
 // The table and update/gethash URLs for Safebrowsing phishing and malware
 // checks.
 pref("urlclassifier.trackingTable", "test-track-simple,mozstd-track-digest256");
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 
 // The table and global pref for blocking access to sites forbidden by policy
 pref("browser.safebrowsing.forbiddenURIs.enabled", false);
 pref("urlclassifier.forbiddenTable", "test-forbid-simple");
 
-pref("browser.safebrowsing.provider.mozilla.lists", "mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256");
+// The table and global pref for blocking plugin content
+pref("browser.safebrowsing.blockedURIs.enabled", false);
+pref("urlclassifier.blockedTable", "test-block-simple,mozplugin-block-digest256");
+
+pref("browser.safebrowsing.provider.mozilla.lists", "mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256");
 pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 // Set to a date in the past to force immediate download in new profiles.
 pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
 // Block lists for tracking protection. The name values will be used as the keys
 // to lookup the localized name in preferences.properties.
 pref("browser.safebrowsing.provider.mozilla.lists.mozstd.name", "mozstdName");
 pref("browser.safebrowsing.provider.mozilla.lists.mozstd.description", "mozstdDesc");
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -199,16 +199,17 @@ class DesktopInstance(GeckoInstance):
         # Bug 1145668 - Has to be reverted to about:blank once Marionette
         # can correctly handle error pages
         'browser.newtab.url': 'about:newtab',
         'browser.newtabpage.enabled': False,
         'browser.reader.detectedFirstArticle': True,
         'browser.safebrowsing.enabled': False,
         'browser.safebrowsing.forbiddenURIs.enabled': False,
         'browser.safebrowsing.malware.enabled': False,
+        'browser.safebrowsing.blockedURIs.enabled': False,
         'browser.search.update': False,
         'browser.tabs.animate': False,
         'browser.tabs.warnOnClose': False,
         'browser.tabs.warnOnOpen': False,
         'browser.uitour.enabled': False,
         'dom.report_all_js_exceptions': True,
         'extensions.getAddons.cache.enabled': False,
         'extensions.installDistroAddons': False,
--- a/testing/talos/talos/config.py
+++ b/testing/talos/talos/config.py
@@ -105,16 +105,17 @@ DEFAULTS = dict(
             'http://127.0.0.1/safebrowsing-dummy/gethash',
         'browser.safebrowsing.provider.mozilla.updateURL':
             'http://127.0.0.1/safebrowsing-dummy/update',
         'privacy.trackingprotection.introURL':
             'http://127.0.0.1/trackingprotection/tour',
         'browser.safebrowsing.enabled': False,
         'browser.safebrowsing.malware.enabled': False,
         'browser.safebrowsing.forbiddenURIs.enabled': False,
+        'browser.safebrowsing.blockedURIs.enabled': False,
         'privacy.trackingprotection.enabled': False,
         'privacy.trackingprotection.pbmode.enabled': False,
         'browser.search.isUS': True,
         'browser.search.countryCode': 'US',
         'browser.selfsupport.url':
             'https://127.0.0.1/selfsupport-dummy/',
         'extensions.update.url':
             'http://127.0.0.1/extensions-dummy/updateURL',
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -10442,10 +10442,24 @@
     "description": "Position in (theoretical) tab cache of tab being switched to"
   },
   "REMOTE_JAR_PROTOCOL_USED": {
     "alert_emails": ["dev-platform@lists.mozilla.org"],
     "expires_in_version": "55",
     "bug_numbers": [1255934],
     "kind": "count",
     "description": "Remote JAR protocol usage"
+  },
+  "PLUGIN_BLOCKED_FOR_STABILITY": {
+    "alert_emails": ["cpeterson@mozilla.com"],
+    "expires_in_version": "52",
+    "kind": "count",
+    "bug_numbers": [1237198],
+    "description": "Count plugins blocked for stability"
+  },
+  "PLUGIN_TINY_CONTENT": {
+    "alert_emails": ["cpeterson@mozilla.com"],
+    "expires_in_version": "52",
+    "kind": "count",
+    "bug_numbers": [1237198],
+    "description": "Count tiny plugin content"
   }
 }
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -49,16 +49,17 @@ function getLists(prefName) {
 // These may be a comma-separated lists of tables.
 const phishingLists = getLists("urlclassifier.phishTable");
 const malwareLists = getLists("urlclassifier.malwareTable");
 const downloadBlockLists = getLists("urlclassifier.downloadBlockTable");
 const downloadAllowLists = getLists("urlclassifier.downloadAllowTable");
 const trackingProtectionLists = getLists("urlclassifier.trackingTable");
 const trackingProtectionWhitelists = getLists("urlclassifier.trackingWhitelistTable");
 const forbiddenLists = getLists("urlclassifier.forbiddenTable");
+const blockedLists = getLists("urlclassifier.blockedTable");
 
 this.SafeBrowsing = {
 
   init: function() {
     if (this.initialized) {
       log("Already initialized");
       return;
     }
@@ -101,24 +102,28 @@ this.SafeBrowsing = {
       this.registerTableWithURLs(trackingProtectionLists[i]);
     }
     for (let i = 0; i < trackingProtectionWhitelists.length; ++i) {
       this.registerTableWithURLs(trackingProtectionWhitelists[i]);
     }
     for (let i = 0; i < forbiddenLists.length; ++i) {
       this.registerTableWithURLs(forbiddenLists[i]);
     }
+    for (let i = 0; i < blockedLists.length; ++i) {
+      this.registerTableWithURLs(blockedLists[i]);
+    }
   },
 
 
   initialized:      false,
   phishingEnabled:  false,
   malwareEnabled:   false,
   trackingEnabled:  false,
   forbiddenEnabled: false,
+  blockedEnabled:   false,
 
   updateURL:             null,
   gethashURL:            null,
 
   reportURL:             null,
 
   getReportURL: function(kind, URI) {
     let pref;
@@ -155,16 +160,17 @@ this.SafeBrowsing = {
   readPrefs: function() {
     log("reading prefs");
 
     this.debug = Services.prefs.getBoolPref("browser.safebrowsing.debug");
     this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.enabled");
     this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
     this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
     this.forbiddenEnabled = Services.prefs.getBoolPref("browser.safebrowsing.forbiddenURIs.enabled");
+    this.blockedEnabled = Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled");
     this.updateProviderURLs();
     this.registerTables();
 
     // XXX The listManager backend gets confused if this is called before the
     // lists are registered. So only call it here when a pref changes, and not
     // when doing initialization. I expect to refactor this later, so pardon the hack.
     if (this.initialized) {
       this.controlUpdateChecking();
@@ -231,17 +237,18 @@ this.SafeBrowsing = {
         log("Update URL given but no lists managed for provider: " + provider);
       }
     }, this);
   },
 
   controlUpdateChecking: function() {
     log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:",
         this.malwareEnabled, "trackingEnabled:", this.trackingEnabled,
-       "forbiddenEnabled:", this.forbiddenEnabled);
+       "forbiddenEnabled:", this.forbiddenEnabled, "blockedEnabled:",
+        this.blockedEnabled);
 
     let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
                       getService(Ci.nsIUrlListManager);
 
     for (let i = 0; i < phishingLists.length; ++i) {
       if (this.phishingEnabled) {
         listManager.enableUpdate(phishingLists[i]);
       } else {
@@ -280,32 +287,40 @@ this.SafeBrowsing = {
     }
     for (let i = 0; i < forbiddenLists.length; ++i) {
       if (this.forbiddenEnabled) {
         listManager.enableUpdate(forbiddenLists[i]);
       } else {
         listManager.disableUpdate(forbiddenLists[i]);
       }
     }
+    for (let i = 0; i < blockedLists.length; ++i) {
+      if (this.blockedEnabled) {
+        listManager.enableUpdate(blockedLists[i]);
+      } else {
+        listManager.disableUpdate(blockedLists[i]);
+      }
+    }
     listManager.maybeToggleUpdateChecking();
   },
 
 
   addMozEntries: function() {
     // Add test entries to the DB.
     // XXX bug 779008 - this could be done by DB itself?
     const phishURL    = "itisatrap.org/firefox/its-a-trap.html";
     const malwareURL  = "itisatrap.org/firefox/its-an-attack.html";
     const unwantedURL = "itisatrap.org/firefox/unwanted.html";
     const trackerURLs = [
       "trackertest.org/",
       "itisatracker.org/",
     ];
-    const whitelistURL = "itisatrap.org/?resource=itisatracker.org";
+    const whitelistURL  = "itisatrap.org/?resource=itisatracker.org";
     const forbiddenURL  = "itisatrap.org/firefox/forbidden.html";
+    const blockedURL    = "itisatrap.org/firefox/blocked.html";
 
     let update = "n:1000\ni:test-malware-simple\nad:1\n" +
                  "a:1:32:" + malwareURL.length + "\n" +
                  malwareURL + "\n";
     update += "n:1000\ni:test-phish-simple\nad:1\n" +
               "a:1:32:" + phishURL.length + "\n" +
               phishURL  + "\n";
     update += "n:1000\ni:test-unwanted-simple\nad:1\n" +
@@ -318,16 +333,19 @@ this.SafeBrowsing = {
                 trackerURL + "\n";
     });
     update += "n:1000\ni:test-trackwhite-simple\nad:1\n" +
               "a:1:32:" + whitelistURL.length + "\n" +
               whitelistURL;
     update += "n:1000\ni:test-forbid-simple\nad:1\n" +
               "a:1:32:" + forbiddenURL.length + "\n" +
               forbiddenURL;
+    update += "n:1000\ni:test-block-simple\nad:1\n" +
+              "a:1:32:" + blockedURL.length + "\n" +
+              blockedURL;
     log("addMozEntries:", update);
 
     let db = Cc["@mozilla.org/url-classifier/dbservice;1"].
              getService(Ci.nsIUrlClassifierDBService);
 
     // nsIUrlClassifierUpdateObserver
     let dummyListener = {
       updateUrlRequested: function() { },
@@ -338,17 +356,17 @@ this.SafeBrowsing = {
         Services.obs.notifyObservers(db, "mozentries-update-finished", "error");
       },
       updateSuccess:      function() {
         Services.obs.notifyObservers(db, "mozentries-update-finished", "success");
       }
     };
 
     try {
-      let tables = "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-forbid-simple";
+      let tables = "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-forbid-simple,test-block-simple";
       db.beginUpdate(dummyListener, tables, "");
       db.beginStream("", "");
       db.updateStream(update);
       db.finishStream();
       db.finishUpdate();
     } catch(ex) {
       // beginUpdate will throw harmlessly if there's an existing update in progress, ignore failures.
       log("addMozEntries failed!", ex);
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -70,28 +70,32 @@ LazyLogModule gUrlClassifierDbServiceLog
 #define CHECK_TRACKING_DEFAULT  false
 
 #define CHECK_TRACKING_PB_PREF    "privacy.trackingprotection.pbmode.enabled"
 #define CHECK_TRACKING_PB_DEFAULT false
 
 #define CHECK_FORBIDDEN_PREF    "browser.safebrowsing.forbiddenURIs.enabled"
 #define CHECK_FORBIDDEN_DEFAULT false
 
+#define CHECK_BLOCKED_PREF    "browser.safebrowsing.blockedURIs.enabled"
+#define CHECK_BLOCKED_DEFAULT false
+
 #define GETHASH_NOISE_PREF      "urlclassifier.gethashnoise"
 #define GETHASH_NOISE_DEFAULT   4
 
 // Comma-separated lists
-#define MALWARE_TABLE_PREF      "urlclassifier.malwareTable"
-#define PHISH_TABLE_PREF        "urlclassifier.phishTable"
-#define TRACKING_TABLE_PREF     "urlclassifier.trackingTable"
-#define TRACKING_WHITELIST_TABLE_PREF "urlclassifier.trackingWhitelistTable"
-#define FORBIDDEN_TABLE_PREF      "urlclassifier.forbiddenTable"
-#define DOWNLOAD_BLOCK_TABLE_PREF "urlclassifier.downloadBlockTable"
-#define DOWNLOAD_ALLOW_TABLE_PREF "urlclassifier.downloadAllowTable"
-#define DISALLOW_COMPLETION_TABLE_PREF "urlclassifier.disallow_completions"
+#define MALWARE_TABLE_PREF              "urlclassifier.malwareTable"
+#define PHISH_TABLE_PREF                "urlclassifier.phishTable"
+#define TRACKING_TABLE_PREF             "urlclassifier.trackingTable"
+#define TRACKING_WHITELIST_TABLE_PREF   "urlclassifier.trackingWhitelistTable"
+#define FORBIDDEN_TABLE_PREF            "urlclassifier.forbiddenTable"
+#define BLOCKED_TABLE_PREF              "urlclassifier.blockedTable"
+#define DOWNLOAD_BLOCK_TABLE_PREF       "urlclassifier.downloadBlockTable"
+#define DOWNLOAD_ALLOW_TABLE_PREF       "urlclassifier.downloadAllowTable"
+#define DISALLOW_COMPLETION_TABLE_PREF  "urlclassifier.disallow_completions"
 
 #define CONFIRM_AGE_PREF        "urlclassifier.max-complete-age"
 #define CONFIRM_AGE_DEFAULT_SEC (45 * 60)
 
 class nsUrlClassifierDBServiceWorker;
 
 // Singleton instance.
 static nsUrlClassifierDBService* sUrlClassifierDBService;
@@ -193,16 +197,19 @@ TablesToResponse(const nsACString& table
     return NS_ERROR_TRACKING_URI;
   }
   if (FindInReadable(NS_LITERAL_CSTRING("-unwanted-"), tables)) {
     return NS_ERROR_UNWANTED_URI;
   }
   if (FindInReadable(NS_LITERAL_CSTRING("-forbid-"), tables)) {
     return NS_ERROR_FORBIDDEN_URI;
   }
+  if (FindInReadable(NS_LITERAL_CSTRING("-block-"), tables)) {
+    return NS_ERROR_BLOCKED_URI;
+  }
   return NS_OK;
 }
 
 static nsCString
 ProcessLookupResults(LookupResultArray* results)
 {
   // Build a stringified list of result tables.
   nsTArray<nsCString> tables;
@@ -1032,17 +1039,18 @@ class nsUrlClassifierClassifyCallback fi
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIURLCLASSIFIERCALLBACK
 
   nsUrlClassifierClassifyCallback(nsIURIClassifierCallback *c,
                                   bool checkMalware,
                                   bool checkPhishing,
                                   bool checkTracking,
-                                  bool checkForbidden)
+                                  bool checkForbidden,
+                                  bool checkBlocked)
     : mCallback(c)
     {}
 
 private:
   ~nsUrlClassifierClassifyCallback() {}
 
   nsCOMPtr<nsIURIClassifierCallback> mCallback;
 };
@@ -1093,16 +1101,17 @@ nsUrlClassifierDBService::GetInstance(ns
 }
 
 
 nsUrlClassifierDBService::nsUrlClassifierDBService()
  : mCheckMalware(CHECK_MALWARE_DEFAULT)
  , mCheckPhishing(CHECK_PHISHING_DEFAULT)
  , mCheckTracking(CHECK_TRACKING_DEFAULT)
  , mCheckForbiddenURIs(CHECK_FORBIDDEN_DEFAULT)
+ , mCheckBlockedURIs(CHECK_BLOCKED_DEFAULT)
  , mInUpdate(false)
 {
 }
 
 nsUrlClassifierDBService::~nsUrlClassifierDBService()
 {
   sUrlClassifierDBService = nullptr;
 }
@@ -1145,16 +1154,22 @@ nsUrlClassifierDBService::ReadTablesFrom
   }
 
   Preferences::GetCString(FORBIDDEN_TABLE_PREF, &tables);
   if (!tables.IsEmpty()) {
     allTables.Append(',');
     allTables.Append(tables);
   }
 
+  Preferences::GetCString(BLOCKED_TABLE_PREF, &tables);
+  if (!tables.IsEmpty()) {
+    allTables.Append(',');
+    allTables.Append(tables);
+  }
+
   Classifier::SplitTables(allTables, mGethashTables);
 
   Preferences::GetCString(DISALLOW_COMPLETION_TABLE_PREF, &tables);
   Classifier::SplitTables(tables, mDisallowCompletionsTables);
 
   return NS_OK;
 }
 
@@ -1176,35 +1191,39 @@ nsUrlClassifierDBService::Init()
     CHECK_MALWARE_DEFAULT);
   mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
     CHECK_PHISHING_DEFAULT);
   mCheckTracking =
     Preferences::GetBool(CHECK_TRACKING_PREF, CHECK_TRACKING_DEFAULT) ||
     Preferences::GetBool(CHECK_TRACKING_PB_PREF, CHECK_TRACKING_PB_DEFAULT);
   mCheckForbiddenURIs = Preferences::GetBool(CHECK_FORBIDDEN_PREF,
     CHECK_FORBIDDEN_DEFAULT);
+  mCheckBlockedURIs = Preferences::GetBool(CHECK_BLOCKED_PREF,
+    CHECK_BLOCKED_DEFAULT);
   uint32_t gethashNoise = Preferences::GetUint(GETHASH_NOISE_PREF,
     GETHASH_NOISE_DEFAULT);
   gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
     CONFIRM_AGE_DEFAULT_SEC);
   ReadTablesFromPrefs();
 
   // Do we *really* need to be able to change all of these at runtime?
   Preferences::AddStrongObserver(this, CHECK_MALWARE_PREF);
   Preferences::AddStrongObserver(this, CHECK_PHISHING_PREF);
   Preferences::AddStrongObserver(this, CHECK_TRACKING_PREF);
   Preferences::AddStrongObserver(this, CHECK_TRACKING_PB_PREF);
   Preferences::AddStrongObserver(this, CHECK_FORBIDDEN_PREF);
+  Preferences::AddStrongObserver(this, CHECK_BLOCKED_PREF);
   Preferences::AddStrongObserver(this, GETHASH_NOISE_PREF);
   Preferences::AddStrongObserver(this, CONFIRM_AGE_PREF);
   Preferences::AddStrongObserver(this, PHISH_TABLE_PREF);
   Preferences::AddStrongObserver(this, MALWARE_TABLE_PREF);
   Preferences::AddStrongObserver(this, TRACKING_TABLE_PREF);
   Preferences::AddStrongObserver(this, TRACKING_WHITELIST_TABLE_PREF);
   Preferences::AddStrongObserver(this, FORBIDDEN_TABLE_PREF);
+  Preferences::AddStrongObserver(this, BLOCKED_TABLE_PREF);
   Preferences::AddStrongObserver(this, DOWNLOAD_BLOCK_TABLE_PREF);
   Preferences::AddStrongObserver(this, DOWNLOAD_ALLOW_TABLE_PREF);
   Preferences::AddStrongObserver(this, DISALLOW_COMPLETION_TABLE_PREF);
 
   // Force PSM loading on main thread
   nsresult rv;
   nsCOMPtr<nsICryptoHash> dummy = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1285,16 +1304,22 @@ nsUrlClassifierDBService::BuildTables(bo
     }
   }
   nsAutoCString forbidden;
   Preferences::GetCString(FORBIDDEN_TABLE_PREF, &forbidden);
   if (mCheckForbiddenURIs && !forbidden.IsEmpty()) {
     tables.Append(',');
     tables.Append(forbidden);
   }
+  nsAutoCString blocked;
+  Preferences::GetCString(BLOCKED_TABLE_PREF, &blocked);
+  if (mCheckBlockedURIs && !blocked.IsEmpty()) {
+    tables.Append(',');
+    tables.Append(blocked);
+  }
 
   if (StringBeginsWith(tables, NS_LITERAL_CSTRING(","))) {
     tables.Cut(0, 1);
   }
 }
 
 // nsChannelClassifier is the only consumer of this interface.
 NS_IMETHODIMP
@@ -1302,24 +1327,25 @@ nsUrlClassifierDBService::Classify(nsIPr
                                    bool aTrackingProtectionEnabled,
                                    nsIURIClassifierCallback* c,
                                    bool* result)
 {
   NS_ENSURE_ARG(aPrincipal);
   NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
 
   if (!(mCheckMalware || mCheckPhishing || aTrackingProtectionEnabled ||
-        mCheckForbiddenURIs)) {
+        mCheckForbiddenURIs || mCheckBlockedURIs)) {
     *result = false;
     return NS_OK;
   }
 
   RefPtr<nsUrlClassifierClassifyCallback> callback =
     new nsUrlClassifierClassifyCallback(c, mCheckMalware, mCheckPhishing,
-                                        mCheckTracking, mCheckForbiddenURIs);
+                                        mCheckTracking, mCheckForbiddenURIs,
+                                        mCheckBlockedURIs);
   if (!callback) return NS_ERROR_OUT_OF_MEMORY;
 
   nsAutoCString tables;
   BuildTables(aTrackingProtectionEnabled, tables);
 
   nsresult rv = LookupURI(aPrincipal, tables, callback, false, result);
   if (rv == NS_ERROR_MALFORMED_URI) {
     *result = false;
@@ -1623,22 +1649,26 @@ nsUrlClassifierDBService::Observe(nsISup
     } else if (NS_LITERAL_STRING(CHECK_TRACKING_PREF).Equals(aData) ||
                NS_LITERAL_STRING(CHECK_TRACKING_PB_PREF).Equals(aData)) {
       mCheckTracking =
         Preferences::GetBool(CHECK_TRACKING_PREF, CHECK_TRACKING_DEFAULT) ||
         Preferences::GetBool(CHECK_TRACKING_PB_PREF, CHECK_TRACKING_PB_DEFAULT);
     } else if (NS_LITERAL_STRING(CHECK_FORBIDDEN_PREF).Equals(aData)) {
       mCheckForbiddenURIs = Preferences::GetBool(CHECK_FORBIDDEN_PREF,
         CHECK_FORBIDDEN_DEFAULT);
+    } else if (NS_LITERAL_STRING(CHECK_BLOCKED_PREF).Equals(aData)) {
+      mCheckBlockedURIs = Preferences::GetBool(CHECK_BLOCKED_PREF,
+        CHECK_BLOCKED_DEFAULT);
     } else if (
       NS_LITERAL_STRING(PHISH_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(MALWARE_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(TRACKING_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(TRACKING_WHITELIST_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(FORBIDDEN_TABLE_PREF).Equals(aData) ||
+      NS_LITERAL_STRING(BLOCKED_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(DOWNLOAD_BLOCK_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(DOWNLOAD_ALLOW_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(DISALLOW_COMPLETION_TABLE_PREF).Equals(aData)) {
       // Just read everything again.
       ReadTablesFromPrefs();
     } else if (NS_LITERAL_STRING(CONFIRM_AGE_PREF).Equals(aData)) {
       gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
         CONFIRM_AGE_DEFAULT_SEC);
@@ -1666,21 +1696,23 @@ nsUrlClassifierDBService::Shutdown()
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->RemoveObserver(CHECK_MALWARE_PREF, this);
     prefs->RemoveObserver(CHECK_PHISHING_PREF, this);
     prefs->RemoveObserver(CHECK_TRACKING_PREF, this);
     prefs->RemoveObserver(CHECK_TRACKING_PB_PREF, this);
     prefs->RemoveObserver(CHECK_FORBIDDEN_PREF, this);
+    prefs->RemoveObserver(CHECK_BLOCKED_PREF, this);
     prefs->RemoveObserver(PHISH_TABLE_PREF, this);
     prefs->RemoveObserver(MALWARE_TABLE_PREF, this);
     prefs->RemoveObserver(TRACKING_TABLE_PREF, this);
     prefs->RemoveObserver(TRACKING_WHITELIST_TABLE_PREF, this);
     prefs->RemoveObserver(FORBIDDEN_TABLE_PREF, this);
+    prefs->RemoveObserver(BLOCKED_TABLE_PREF, this);
     prefs->RemoveObserver(DOWNLOAD_BLOCK_TABLE_PREF, this);
     prefs->RemoveObserver(DOWNLOAD_ALLOW_TABLE_PREF, this);
     prefs->RemoveObserver(DISALLOW_COMPLETION_TABLE_PREF, this);
     prefs->RemoveObserver(CONFIRM_AGE_PREF, this);
   }
 
   DebugOnly<nsresult> rv;
   // First close the db connection.
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h
@@ -117,16 +117,20 @@ private:
   // TRUE if the nsURIClassifier implementation should check for tracking
   // uris on document loads.
   bool mCheckTracking;
 
   // TRUE if the nsURIClassifier implementation should check for forbidden
   // uris on document loads.
   bool mCheckForbiddenURIs;
 
+  // TRUE if the nsURIClassifier implementation should check for blocked
+  // uris on document loads.
+  bool mCheckBlockedURIs;
+
   // TRUE if a BeginUpdate() has been called without an accompanying
   // CancelUpdate()/FinishUpdate().  This is used to prevent competing
   // updates, not to determine whether an update is still being
   // processed.
   bool mInUpdate;
 
   // The list of tables that can use the default hash completer object.
   nsTArray<nsCString> mGethashTables;
--- a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
+++ b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
@@ -24,16 +24,17 @@ var secMan = Cc["@mozilla.org/scriptsecu
 
 // Disable hashcompleter noise for tests
 var prefBranch = Cc["@mozilla.org/preferences-service;1"].
                  getService(Ci.nsIPrefBranch);
 prefBranch.setIntPref("urlclassifier.gethashnoise", 0);
 
 // Enable malware/phishing checking for tests
 prefBranch.setBoolPref("browser.safebrowsing.malware.enabled", true);
+prefBranch.setBoolPref("browser.safebrowsing.blockedURIs.enabled", true);
 prefBranch.setBoolPref("browser.safebrowsing.enabled", true);
 
 // Enable all completions for tests
 prefBranch.setCharPref("urlclassifier.disallow_completions", "");
 
 // Hash completion timeout
 prefBranch.setIntPref("urlclassifier.gethash.timeout_ms", 5000);
 
@@ -50,35 +51,38 @@ function delFile(name) {
 
 function cleanUp() {
   delFile("urlclassifier3.sqlite");
   delFile("safebrowsing/classifier.hashkey");
   delFile("safebrowsing/test-phish-simple.sbstore");
   delFile("safebrowsing/test-malware-simple.sbstore");
   delFile("safebrowsing/test-unwanted-simple.sbstore");
   delFile("safebrowsing/test-forbid-simple.sbstore");
+  delFile("safebrowsing/test-block-simple.sbstore");
   delFile("safebrowsing/test-track-simple.sbstore");
   delFile("safebrowsing/test-trackwhite-simple.sbstore");
   delFile("safebrowsing/test-phish-simple.cache");
   delFile("safebrowsing/test-malware-simple.cache");
   delFile("safebrowsing/test-unwanted-simple.cache");
   delFile("safebrowsing/test-forbid-simple.cache");
+  delFile("safebrowsing/test-block-simple.cache");
   delFile("safebrowsing/test-track-simple.cache");
   delFile("safebrowsing/test-trackwhite-simple.cache");
   delFile("safebrowsing/test-phish-simple.pset");
   delFile("safebrowsing/test-malware-simple.pset");
   delFile("safebrowsing/test-unwanted-simple.pset");
   delFile("safebrowsing/test-forbid-simple.pset");
+  delFile("safebrowsing/test-block-simple.pset");
   delFile("safebrowsing/test-track-simple.pset");
   delFile("safebrowsing/test-trackwhite-simple.pset");
   delFile("testLarge.pset");
   delFile("testNoDelta.pset");
 }
 
-var allTables = "test-phish-simple,test-malware-simple,test-unwanted-simple,test-forbid-simple,test-track-simple,test-trackwhite-simple";
+var allTables = "test-phish-simple,test-malware-simple,test-unwanted-simple,test-forbid-simple,test-track-simple,test-trackwhite-simple,test-block-simple";
 
 var dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(Ci.nsIUrlClassifierDBService);
 var streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
                     .getService(Ci.nsIUrlClassifierStreamUpdater);
 
 
 /*
  * Builds an update from an object that looks like:
@@ -129,16 +133,20 @@ function buildMalwareUpdate(chunks, hash
 function buildUnwantedUpdate(chunks, hashSize) {
   return buildUpdate({"test-unwanted-simple" : chunks}, hashSize);
 }
 
 function buildForbiddenUpdate(chunks, hashSize) {
   return buildUpdate({"test-forbid-simple" : chunks}, hashSize);
 }
 
+function buildBlockedUpdate(chunks, hashSize) {
+  return buildUpdate({"test-block-simple" : chunks}, hashSize);
+}
+
 function buildBareUpdate(chunks, hashSize) {
   return buildUpdate({"" : chunks}, hashSize);
 }
 
 /**
  * Performs an update of the dbservice manually, bypassing the stream updater
  */
 function doSimpleUpdate(updateText, success, failure) {
@@ -261,16 +269,21 @@ unwantedUrlsExist: function(urls, cb)
   this.checkUrls(urls, 'test-unwanted-simple', cb);
 },
 
 forbiddenUrlsExist: function(urls, cb)
 {
   this.checkUrls(urls, 'test-forbid-simple', cb);
 },
 
+blockedUrlsExist: function(urls, cb)
+{
+  this.checkUrls(urls, 'test-block-simple', cb);
+},
+
 subsDontExist: function(urls, cb)
 {
   // XXX: there's no interface for checking items in the subs table
   cb();
 },
 
 subsExist: function(urls, cb)
 {
--- a/toolkit/components/url-classifier/tests/unit/test_dbservice.js
+++ b/toolkit/components/url-classifier/tests/unit/test_dbservice.js
@@ -42,26 +42,33 @@ var chunk5Urls = [
 var chunk5 = chunk5Urls.join("\n");
 
 var chunk6Urls = [
   "h.com/i",
   "j.com/k",
   ];
 var chunk6 = chunk6Urls.join("\n");
 
+var chunk7Urls = [
+  "l.com/m",
+  "n.com/o",
+  ];
+var chunk7 = chunk7Urls.join("\n");
+
 // we are going to add chunks 1, 2, 4, 5, and 6 to phish-simple,
-// chunk 2 to malware-simple, chunk 3 to unwanted-simple and
-// chunk 4 to forbid-simple.
+// chunk 2 to malware-simple, chunk 3 to unwanted-simple,
+// chunk 4 to forbid-simple, and chunk 7 to block-simple.
 // Then we'll remove the urls in chunk3 from phish-simple, then
-// expire chunk 1 and chunks 4-6 from phish-simple.
+// expire chunk 1 and chunks 4-7 from phish-simple.
 var phishExpected = {};
 var phishUnexpected = {};
 var malwareExpected = {};
 var unwantedExpected = {};
 var forbiddenExpected = {};
+var blockedExpected = {};
 for (var i = 0; i < chunk2Urls.length; i++) {
   phishExpected[chunk2Urls[i]] = true;
   malwareExpected[chunk2Urls[i]] = true;
 }
 for (var i = 0; i < chunk3Urls.length; i++) {
   unwantedExpected[chunk3Urls[i]] = true;
   delete phishExpected[chunk3Urls[i]];
   phishUnexpected[chunk3Urls[i]] = true;
@@ -78,16 +85,21 @@ for (var i = 0; i < chunk4Urls.length; i
 for (var i = 0; i < chunk5Urls.length; i++) {
   // chunk5 urls are expired
   phishUnexpected[chunk5Urls[i]] = true;
 }
 for (var i = 0; i < chunk6Urls.length; i++) {
   // chunk6 urls are expired
   phishUnexpected[chunk6Urls[i]] = true;
 }
+for (var i = 0; i < chunk7Urls.length; i++) {
+  blockedExpected[chunk7Urls[i]] = true;
+  // chunk7 urls are expired
+  phishUnexpected[chunk7Urls[i]] = true;
+}
 
 // Check that the entries hit based on sub-parts
 phishExpected["baz.com/foo/bar"] = true;
 phishExpected["foo.bar.baz.com/foo"] = true;
 phishExpected["bar.baz.com/"] = true;
 
 var numExpecting;
 
@@ -115,17 +127,17 @@ function checkNoHost()
 function tablesCallbackWithoutSub(tables)
 {
   var parts = tables.split("\n");
   parts.sort();
 
   // there's a leading \n here because splitting left an empty string
   // after the trailing newline, which will sort first
   do_check_eq(parts.join("\n"),
-              "\ntest-forbid-simple;a:1\ntest-malware-simple;a:1\ntest-phish-simple;a:2\ntest-unwanted-simple;a:1");
+              "\ntest-block-simple;a:1\ntest-forbid-simple;a:1\ntest-malware-simple;a:1\ntest-phish-simple;a:2\ntest-unwanted-simple;a:1");
 
   checkNoHost();
 }
 
 
 function expireSubSuccess(result) {
   dbservice.getTables(tablesCallbackWithoutSub);
 }
@@ -133,17 +145,17 @@ function expireSubSuccess(result) {
 function tablesCallbackWithSub(tables)
 {
   var parts = tables.split("\n");
   parts.sort();
 
   // there's a leading \n here because splitting left an empty string
   // after the trailing newline, which will sort first
   do_check_eq(parts.join("\n"),
-              "\ntest-forbid-simple;a:1\ntest-malware-simple;a:1\ntest-phish-simple;a:2:s:3\ntest-unwanted-simple;a:1");
+              "\ntest-block-simple;a:1\ntest-forbid-simple;a:1\ntest-malware-simple;a:1\ntest-phish-simple;a:2:s:3\ntest-unwanted-simple;a:1");
 
   // verify that expiring a sub chunk removes its name from the list
   var data =
     "n:1000\n" +
     "i:test-phish-simple\n" +
     "sd:3\n";
 
   doSimpleUpdate(data, expireSubSuccess, testFailure);
@@ -202,16 +214,26 @@ function forbiddenExists(result) {
 
   try {
     do_check_true(result.indexOf("test-forbid-simple") != -1);
   } finally {
     checkDone();
   }
 }
 
+function blockedExists(result) {
+  dumpn("blockedExists: " + result);
+
+  try {
+    do_check_true(result.indexOf("test-block-simple") != -1);
+  } finally {
+    checkDone();
+  }
+}
+
 function checkState()
 {
   numExpecting = 0;
 
 
   for (var key in phishExpected) {
     var principal = secMan.createCodebasePrincipal(iosvc.newURI("http://" + key, null, null), {});
     dbservice.lookup(principal, allTables, phishExists, true);
@@ -236,16 +258,22 @@ function checkState()
     numExpecting++;
   }
 
   for (var key in forbiddenExpected) {
     var principal = secMan.createCodebasePrincipal(iosvc.newURI("http://" + key, null, null), {});
     dbservice.lookup(principal, allTables, forbiddenExists, true);
     numExpecting++;
   }
+
+  for (var key in blockedExpected) {
+    var principal = secMan.createCodebasePrincipal(iosvc.newURI("http://" + key, null, null), {});
+    dbservice.lookup(principal, allTables, blockedExists, true);
+    numExpecting++;
+  }
 }
 
 function testSubSuccess(result)
 {
   do_check_eq(result, "1000");
   checkState();
 }
 
@@ -288,17 +316,20 @@ function do_adds() {
     "i:test-malware-simple\n" +
     "a:1:32:" + chunk2.length + "\n" +
     chunk2 + "\n" +
     "i:test-unwanted-simple\n" +
     "a:1:32:" + chunk3.length + "\n" +
     chunk3 + "\n" +
     "i:test-forbid-simple\n" +
     "a:1:32:" + chunk4.length + "\n" +
-    chunk4 + "\n";
+    chunk4 + "\n" +
+    "i:test-block-simple\n" +
+    "a:1:32:" + chunk7.length + "\n" +
+    chunk7 + "\n";
 
   doSimpleUpdate(data, testAddSuccess, testFailure);
 }
 
 function run_test() {
   do_adds();
   do_test_pending();
 }
--- a/toolkit/components/url-classifier/tests/unit/test_streamupdater.js
+++ b/toolkit/components/url-classifier/tests/unit/test_streamupdater.js
@@ -128,16 +128,17 @@ function testErrorUrlForward() {
 }
 
 function testMultipleTables() {
   var add1Urls = [ "foo-multiple.com/a", "bar-multiple.com/c" ];
   var add2Urls = [ "foo-multiple.com/b" ];
   var add3Urls = [ "bar-multiple.com/d" ];
   var add4Urls = [ "bar-multiple.com/e" ];
   var add5Urls = [ "bar-multiple.com/f" ];
+  var add6Urls = [ "bar-multiple.com/g" ];
 
   var update = "n:1000\n";
   update += "i:test-phish-simple\n";
 
   var update1 = buildBareUpdate(
     [{ "chunkNum" : 1,
        "urls" : add1Urls }]);
   update += "u:data:," + encodeURIComponent(update1) + "\n";
@@ -161,22 +162,29 @@ function testMultipleTables() {
   update += "u:data:," + encodeURIComponent(update4) + "\n";
 
   update += "i:test-forbid-simple\n";
   var update5 = buildBareUpdate(
     [{ "chunkNum" : 5,
        "urls" : add5Urls }]);
   update += "u:data:," + encodeURIComponent(update5) + "\n";
 
+  update += "i:test-block-simple\n";
+  var update6 = buildBareUpdate(
+    [{ "chunkNum" : 6,
+       "urls" : add6Urls }]);
+  update += "u:data:," + encodeURIComponent(update6) + "\n";
+
   var assertions = {
-    "tableData" : "test-forbid-simple;a:5\ntest-malware-simple;a:3\ntest-phish-simple;a:1-2\ntest-unwanted-simple;a:4",
+    "tableData" : "test-block-simple;a:6\ntest-forbid-simple;a:5\ntest-malware-simple;a:3\ntest-phish-simple;a:1-2\ntest-unwanted-simple;a:4",
     "urlsExist" : add1Urls.concat(add2Urls),
     "malwareUrlsExist" : add3Urls,
     "unwantedUrlsExist" : add4Urls,
-    "forbiddenUrlsExist" : add5Urls
+    "forbiddenUrlsExist" : add5Urls,
+    "blockedUrlsExist" : add6Urls
   };
 
   doTest([update], assertions, false);
 }
 
 function Observer(callback) {
   this.observe = callback;
 }
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -712,16 +712,17 @@
   ERROR(NS_ERROR_WONT_HANDLE_CONTENT,   FAILURE(1)),
   /* The load has been cancelled because it was found on a malware or phishing
    * blacklist. */
   ERROR(NS_ERROR_MALWARE_URI,           FAILURE(30)),
   ERROR(NS_ERROR_PHISHING_URI,          FAILURE(31)),
   ERROR(NS_ERROR_TRACKING_URI,          FAILURE(34)),
   ERROR(NS_ERROR_UNWANTED_URI,          FAILURE(35)),
   ERROR(NS_ERROR_FORBIDDEN_URI,         FAILURE(36)),
+  ERROR(NS_ERROR_BLOCKED_URI,           FAILURE(37)),
   /* Used when "Save Link As..." doesn't see the headers quickly enough to
    * choose a filename.  See nsContextMenu.js. */
   ERROR(NS_ERROR_SAVE_LINK_AS_TIMEOUT,  FAILURE(32)),
   /* Used when the data from a channel has already been parsed and cached so it
    * doesn't need to be reparsed from the original source. */
   ERROR(NS_ERROR_PARSED_DATA_CACHED,    FAILURE(33)),
 
   /* This success code indicates that a refresh header was found and