Bug 1216723 - Add a new -forbid- Safe Browsing list type. r=gcp,r=smaug
authorFrancois Marier <francois@mozilla.com>
Fri, 20 Nov 2015 20:24:50 -0800
changeset 273694 d7c0dbcf2a51c3d1973d2008db2b28f3e208f166
parent 273693 eaa63e3f270780d6f7474f3e3153f896d6b49516
child 273695 0b2a4b521e6bff5bba5bf8a61c935d48b66cd3ac
push id29710
push usercbook@mozilla.com
push dateMon, 23 Nov 2015 13:09:07 +0000
treeherdermozilla-central@d3d286102ba7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgcp, smaug
bugs1216723
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1216723 - Add a new -forbid- Safe Browsing list type. r=gcp,r=smaug
b2g/locales/en-US/chrome/overrides/appstrings.properties
browser/base/content/aboutNetError.xhtml
browser/base/content/blockedSite.xhtml
browser/base/content/browser.js
browser/base/content/content.js
browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
browser/locales/en-US/chrome/overrides/appstrings.properties
browser/locales/en-US/chrome/overrides/netError.dtd
docshell/base/nsDocShell.cpp
docshell/resources/content/netError.xhtml
dom/browser-element/BrowserElementChildPreload.js
dom/locales/en-US/chrome/appstrings.properties
dom/locales/en-US/chrome/netError.dtd
mobile/android/chrome/content/blockedSite.xhtml
mobile/android/chrome/content/browser.js
mobile/locales/en-US/overrides/appstrings.properties
modules/libpref/init/all.js
toolkit/components/url-classifier/SafeBrowsing.jsm
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.h
webapprt/locales/en-US/webapprt/overrides/appstrings.properties
xpcom/base/ErrorList.h
--- a/b2g/locales/en-US/chrome/overrides/appstrings.properties
+++ b/b2g/locales/en-US/chrome/overrides/appstrings.properties
@@ -27,13 +27,14 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
+forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -418,16 +418,17 @@
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
         <h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
+        <h1 id="et_forbiddenBlocked">&forbiddenBlocked.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
         <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
         <h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
@@ -447,16 +448,17 @@
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
         <div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
+        <div id="ed_forbiddenBlocked">&forbiddenBlocked.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
         <div id="ed_sslv3Used">&sslv3Used.longDesc;</div>
         <div id="learn_more_ssl3">&sslv3Used.learnMore;</div>
         <div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc;</div>
         <div id="learn_more_weak_crypto">&weakCryptoUsed.learnMore;</div>
       </div>
--- a/browser/base/content/blockedSite.xhtml
+++ b/browser/base/content/blockedSite.xhtml
@@ -63,120 +63,81 @@
       function getHostString()
       {
         try {
           return document.location.hostname;
         } catch (e) {
           return getURL();
         }
       }
-      
+
       function initPage()
       {
-        // Handoff to the appropriate initializer, based on error code
+        var error = "";
         switch (getErrorCode()) {
           case "malwareBlocked" :
-            initPage_malware();
+            error = "malware";
             break;
           case "phishingBlocked" :
-            initPage_phishing();
+            error = "phishing";
             break;
           case "unwantedBlocked" :
-            initPage_unwanted();
+            error = "unwanted";
             break;
+          case "forbiddenBlocked" :
+            error = "forbidden";
+            break;
+          default:
+            return;
         }
-      }        
-      
-      /**
-       * Initialize custom strings and functionality for blocked malware case
-       */
-      function initPage_malware()
-      {
-        // Remove phishing and unwanted strings
-        var el = document.getElementById("errorTitleText_phishing");
-        el.parentNode.removeChild(el);
+
+        var el;
+
+        if (error !== "malware") {
+          el = document.getElementById("errorTitleText_malware");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_malware");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_malware");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorShortDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_phishing");
-        el.parentNode.removeChild(el);
+        if (error !== "phishing") {
+          el = document.getElementById("errorTitleText_phishing");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_phishing");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_phishing");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorTitleText_unwanted");
-        el.parentNode.removeChild(el);
+        if (error !== "unwanted") {
+          el = document.getElementById("errorTitleText_unwanted");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_unwanted");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_unwanted");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorShortDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_unwanted");
-        el.parentNode.removeChild(el);
+        if (error !== "forbidden") {
+          el = document.getElementById("errorTitleText_forbidden");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_forbidden");
+          el.parentNode.removeChild(el);
+        } else {
+          el = document.getElementById("ignoreWarningButton");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("reportButton");
+          el.parentNode.removeChild(el);
+        }
 
         // Set sitename
-        document.getElementById("malware_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_malware")
-                                 .innerHTML;
-      }
-      
-      /**
-       * Initialize custom strings and functionality for blocked malware case
-       */
-      function initPage_unwanted()
-      {
-        // Remove phishing and malware strings
-        var el = document.getElementById("errorTitleText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorTitleText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_malware");
-        el.parentNode.removeChild(el);
-
-        // Set sitename
-        document.getElementById("unwanted_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_unwanted")
-                                 .innerHTML;
-      }
-      
-      /**
-       * Initialize custom strings and functionality for blocked phishing case
-       */
-      function initPage_phishing()
-      {
-        // Remove malware and unwanted strings
-        var el = document.getElementById("errorTitleText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorTitleText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        // Set sitename
-        document.getElementById("phishing_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_phishing")
+        document.getElementById(error + "_sitename").textContent = getHostString();
+        document.title = document.getElementById("errorTitleText_" + error)
                                  .innerHTML;
       }
     ]]></script>
     <style type="text/css">
       /* Style warning button to look like a small text link in the
          bottom right. This is preferable to just using a text link
          since there is already a mechanism in browser.js for trapping
          oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
@@ -208,25 +169,27 @@
   <body dir="&locale.dir;">
     <div id="errorPageContainer">
     
       <!-- Error Title -->
       <div id="errorTitle">
         <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title;</h1>
         <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
         <h1 id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
+        <h1 id="errorTitleText_forbidden">&safeb.blocked.forbiddenPage.title;</h1>
       </div>
       
       <div id="errorLongContent">
       
         <!-- Short Description -->
         <div id="errorShortDesc">
           <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc;</p>
           <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
           <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
+          <p id="errorShortDescText_forbidden">&safeb.blocked.forbiddenPage.shortDesc;</p>
         </div>
 
         <!-- Long Description -->
         <div id="errorLongDesc">
           <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc;</p>
           <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
           <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
         </div>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2993,44 +2993,55 @@ var BrowserOnClick = {
         break;
 
     }
   },
 
   onAboutBlocked: function (elementId, reason, isTopFrame, location) {
     // Depending on what page we are displaying here (malware/phishing/unwanted)
     // use the right strings and links for each.
-    let bucketName = "WARNING_PHISHING_PAGE_";
+    let bucketName = "";
+    let sendTelemetry = false;
     if (reason === 'malware') {
+      sendTelemetry = true;
       bucketName = "WARNING_MALWARE_PAGE_";
+    } else if (reason === 'phishing') {
+      sendTelemetry = true;
+      bucketName = "WARNING_PHISHING_PAGE_";
     } else if (reason === 'unwanted') {
+      sendTelemetry = true;
       bucketName = "WARNING_UNWANTED_PAGE_";
     }
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
     let nsISecTel = Ci.nsISecurityUITelemetry;
     bucketName += isTopFrame ? "TOP_" : "FRAME_";
     switch (elementId) {
       case "getMeOutButton":
-        secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
+        if (sendTelemetry) {
+          secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
+        }
         getMeOutOfHere();
         break;
 
       case "reportButton":
         // This is the "Why is this site blocked" button. We redirect
         // to the generic page describing phishing/malware protection.
 
         // We log even if malware/phishing/unwanted info URL couldn't be found:
         // the measurement is for how many users clicked the WHY BLOCKED button
-        secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
-
+        if (sendTelemetry) {
+          secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
+        }
         openHelpLink("phishing-malware", false, "current");
         break;
 
       case "ignoreWarningButton":
-        secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
+        if (sendTelemetry) {
+          secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
+        }
         this.ignoreWarningButton(reason);
         break;
     }
   },
 
   /**
    * This functions prevents navigation from happening directly through the <a>
    * link in about:newtab (which is loaded in the parent and therefore would load
@@ -3089,16 +3100,18 @@ var BrowserOnClick = {
         callback: function() {
           openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');
         }
       };
     } else if (reason === 'unwanted') {
       title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
       // There is no button for reporting errors since Google doesn't currently
       // provide a URL endpoint for these reports.
+    } else {
+      return; // no notifications for forbidden sites
     }
 
     let notificationBox = gBrowser.getNotificationBox();
     let value = "blocked-badware-page";
 
     let previousNotification = notificationBox.getNotificationWithValue(value);
     if (previousNotification) {
       notificationBox.removeNotification(previousNotification);
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -455,16 +455,18 @@ var ClickEventHandler = {
   },
 
   onAboutBlocked: function (targetElement, ownerDoc) {
     var reason = 'phishing';
     if (/e=malwareBlocked/.test(ownerDoc.documentURI)) {
       reason = 'malware';
     } else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
       reason = 'unwanted';
+    } else if (/e=forbiddenBlocked/.test(ownerDoc.documentURI)) {
+      reason = 'forbidden';
     }
     sendAsyncMessage("Browser:SiteBlockedError", {
       location: ownerDoc.location.href,
       reason: reason,
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
     });
   },
--- a/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
+++ b/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
@@ -3,21 +3,26 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY safeb.palm.accept.label "Get me out of here!">
 <!ENTITY safeb.palm.decline.label "Ignore this warning">
 <!ENTITY safeb.palm.notforgery.label2 "This isn't a web forgery…">
 <!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
 
 <!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
-<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!-- Localization note (safeb.blocked.malwarePage.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
 
 <!ENTITY safeb.blocked.unwantedPage.title "Reported Unwanted Software Page!">
-<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!-- Localization note (safeb.blocked.unwantedPage.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.unwantedPage.shortDesc "This web page at <span id='unwanted_sitename'/> has been reported to contain unwanted software and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.unwantedPage.longDesc "<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>">
 
 <!ENTITY safeb.blocked.phishingPage.title "Reported Web Forgery!">
-<!-- Localization note (safeb.blocked.phishing.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!-- Localization note (safeb.blocked.phishingPage.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.phishingPage.shortDesc "This web page at <span id='phishing_sitename'/> has been reported as a web forgery and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.phishingPage.longDesc "<p>Web forgeries are designed to trick you into revealing personal or financial information by imitating sources you may trust.</p><p>Entering any information on this web page may result in identity theft or other fraud.</p>">
+
+<!ENTITY safeb.blocked.forbiddenPage.title "Forbidden Site">
+<!-- Localization note (safeb.blocked.forbiddenPage.shortDesc) - Please don't translate the contents of the <span id="forbidden_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.forbiddenPage.shortDesc "This Web page at <span id='forbidden_sitename'/> has been blocked based on your browser configuration.">
+
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -27,15 +27,16 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
+forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 ## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
 ## LOCALIZATION NOTE (weakCryptoUsed) - Do not translate "%S".
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -170,16 +170,20 @@ be temporary, and you can try again late
 ">
 
 <!ENTITY phishingBlocked.title "Suspected Web Forgery!">
 <!ENTITY phishingBlocked.longDesc "
 <p>Entering any personal information on this page may result in identity theft or other fraud.</p>
 <p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
 ">
 
+<!ENTITY forbiddenBlocked.title "Forbidden Site">
+<!ENTITY forbiddenBlocked.longDesc "<p>&brandShortName; prevented this page from loading because it is configured to block it.</p>
+">
+
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
 <!ENTITY corruptedContentError.title "Corrupted Content Error">
 <!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
 
 <!ENTITY securityOverride.linkText "Or you can add an exception…">
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4904,46 +4904,53 @@ nsDocShell::DisplayLoadError(nsresult aE
         }
 
       } else {
         error.AssignLiteral("nssFailure2");
       }
     }
   } else if (NS_ERROR_PHISHING_URI == aError ||
              NS_ERROR_MALWARE_URI == aError ||
-             NS_ERROR_UNWANTED_URI == aError) {
+             NS_ERROR_UNWANTED_URI == aError ||
+             NS_ERROR_FORBIDDEN_URI == aError) {
     nsAutoCString host;
     aURI->GetHost(host);
     CopyUTF8toUTF16(host, formatStrs[0]);
     formatStrCount = 1;
 
     // Malware and phishing detectors may want to use an alternate error
     // page, but if the pref's not set, we'll fall back on the standard page
     nsAdoptingCString alternateErrorPage =
       Preferences::GetCString("urlclassifier.alternate_error_page");
     if (alternateErrorPage) {
       errorPage.Assign(alternateErrorPage);
     }
 
     uint32_t bucketId;
+    bool sendTelemetry = false;
     if (NS_ERROR_PHISHING_URI == aError) {
+      sendTelemetry = true;
       error.AssignLiteral("phishingBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP;
     } else if (NS_ERROR_MALWARE_URI == aError) {
+      sendTelemetry = true;
       error.AssignLiteral("malwareBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP;
-    } else {
+    } else if (NS_ERROR_UNWANTED_URI == aError) {
+      sendTelemetry = true;
       error.AssignLiteral("unwantedBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP;
-    }
-
-    if (errorPage.EqualsIgnoreCase("blocked")) {
+    } else if (NS_ERROR_FORBIDDEN_URI == aError) {
+      error.AssignLiteral("forbiddenBlocked");
+    }
+
+    if (sendTelemetry && errorPage.EqualsIgnoreCase("blocked")) {
       Telemetry::Accumulate(Telemetry::SECURITY_UI, bucketId);
     }
 
     cssClass.AssignLiteral("blacklist");
   } else if (NS_ERROR_CONTENT_CRASHED == aError) {
     errorPage.AssignLiteral("tabcrashed");
     error.AssignLiteral("tabcrashed");
 
@@ -7694,16 +7701,17 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
                aStatus == NS_ERROR_REDIRECT_LOOP ||
                aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
                aStatus == NS_ERROR_NET_INTERRUPT ||
                aStatus == NS_ERROR_NET_RESET ||
                aStatus == NS_ERROR_OFFLINE ||
                aStatus == NS_ERROR_MALWARE_URI ||
                aStatus == NS_ERROR_PHISHING_URI ||
                aStatus == NS_ERROR_UNWANTED_URI ||
+               aStatus == NS_ERROR_FORBIDDEN_URI ||
                aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
                aStatus == NS_ERROR_REMOTE_XUL ||
                aStatus == NS_ERROR_INTERCEPTION_FAILED ||
                NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
       // Errors to be shown for any frame
       DisplayLoadError(aStatus, url, nullptr, aChannel);
     } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
       // Non-caching channels will simply return NS_ERROR_OFFLINE.
--- a/docshell/resources/content/netError.xhtml
+++ b/docshell/resources/content/netError.xhtml
@@ -290,16 +290,17 @@
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
         <h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
+        <h1 id="et_forbiddenBlocked">&forbiddenBlocked.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
@@ -317,16 +318,17 @@
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
         <div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
+        <div id="ed_forbiddenBlocked">&forbiddenBlocked.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -2032,16 +2032,19 @@ BrowserElementChild.prototype = {
             sendAsyncMsg('error', { type: 'phishingBlocked' });
             return;
           case Cr.NS_ERROR_MALWARE_URI :
             sendAsyncMsg('error', { type: 'malwareBlocked' });
             return;
           case Cr.NS_ERROR_UNWANTED_URI :
             sendAsyncMsg('error', { type: 'unwantedBlocked' });
             return;
+          case Cr.NS_ERROR_FORBIDDEN_URI :
+            sendAsyncMsg('error', { type: 'forbiddenBlocked' });
+            return;
 
           case Cr.NS_ERROR_OFFLINE :
             sendAsyncMsg('error', { type: 'offline' });
             return;
           case Cr.NS_ERROR_MALFORMED_URI :
             sendAsyncMsg('error', { type: 'malformedURI' });
             return;
           case Cr.NS_ERROR_REDIRECT_LOOP :
--- a/dom/locales/en-US/chrome/appstrings.properties
+++ b/dom/locales/en-US/chrome/appstrings.properties
@@ -26,13 +26,14 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
+forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default.
 sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol.
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, the connection to this website has not been established.
--- a/dom/locales/en-US/chrome/netError.dtd
+++ b/dom/locales/en-US/chrome/netError.dtd
@@ -87,16 +87,20 @@
 ">
 
 <!ENTITY phishingBlocked.title "Suspected Web Forgery!">
 <!ENTITY phishingBlocked.longDesc "
 <p>Entering any personal information on this page may result in identity theft or other fraud.</p>
 <p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
 ">
 
+<!ENTITY forbiddenBlocked.title "Forbidden Site">
+<!ENTITY forbiddenBlocked.longDesc "<p>The browser prevented this page from loading because it is configured to block it.</p>
+">
+
 <!ENTITY securityOverride.linkText "Or you can add an exception…">
 <!ENTITY securityOverride.warningContent "
 <p>You should not add an exception if you are using an internet connection that you do not trust completely or if you are not used to seeing a warning for this server.</p>
 <p>If you still wish to add an exception for this site, you can do so in your advanced encryption settings.</p>
 ">
 
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>The browser prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
--- a/mobile/android/chrome/content/blockedSite.xhtml
+++ b/mobile/android/chrome/content/blockedSite.xhtml
@@ -64,133 +64,106 @@
       function getHostString()
       {
         try {
           return document.location.hostname;
         } catch (e) {
           return getURL();
         }
       }
-      
+
       function initPage()
       {
-        // Handoff to the appropriate initializer, based on error code
+        var error = "";
         switch (getErrorCode()) {
           case "malwareBlocked" :
-            initPage_malware();
+            error = "malware";
             break;
           case "phishingBlocked" :
-            initPage_phishing();
+            error = "phishing";
             break;
           case "unwantedBlocked" :
-            initPage_unwanted();
+            error = "unwanted";
+            break;
+          case "forbiddenBlocked" :
+            error = "forbidden";
             break;
+          default:
+            return;
+        }
+
+        var el;
+
+        if (error !== "malware") {
+          el = document.getElementById("errorTitleText_malware");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_malware");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_malware");
+          el.parentNode.removeChild(el);
         }
-      }        
-      
-      /**
-       * Initialize custom strings and functionality for blocked malware case
-       */
-      function initPage_malware()
-      {
-        // Remove phishing/unwanted strings
-        var el = document.getElementById("errorTitleText_phishing");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorTitleText_unwanted");
-        el.parentNode.removeChild(el);
+
+        if (error !== "phishing") {
+          el = document.getElementById("errorTitleText_phishing");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_phishing");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_phishing");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorShortDescText_phishing");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorShortDescText_unwanted");
-        el.parentNode.removeChild(el);
+        if (error !== "unwanted") {
+          el = document.getElementById("errorTitleText_unwanted");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_unwanted");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_unwanted");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorLongDescText_phishing");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorLongDescText_unwanted");
-        el.parentNode.removeChild(el);
+        if (error !== "forbidden") {
+          el = document.getElementById("errorTitleText_forbidden");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_forbidden");
+          el.parentNode.removeChild(el);
+        } else {
+          el = document.getElementById("ignoreWarningButton");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("reportButton");
+          el.parentNode.removeChild(el);
+        }
 
         // Set sitename
-        document.getElementById("malware_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_malware")
-                                 .innerHTML;
-      }
-      
-      /**
-       * Initialize custom strings and functionality for blocked phishing case
-       */
-      function initPage_phishing()
-      {
-        // Remove malware/unwanted strings
-        var el = document.getElementById("errorTitleText_malware");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorTitleText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_malware");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorShortDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_malware");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorLongDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        document.title = document.getElementById("errorTitleText_phishing")
-                                 .innerHTML;
-      }
-
-      /**
-       * Initialize custom strings and functionality for blocked unwanted
-       * software case
-       */
-      function initPage_unwanted()
-      {
-        // Remove malware/phishing strings
-        var el = document.getElementById("errorTitleText_malware");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorTitleText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_malware");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorShortDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_malware");
-        el.parentNode.removeChild(el);
-        el = document.getElementById("errorLongDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        // Set sitename
-        document.getElementById("unwanted_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_unwanted")
+        document.getElementById(error + "_sitename").textContent = getHostString();
+        document.title = document.getElementById("errorTitleText_" + error)
                                  .innerHTML;
       }
     ]]></script>
   </head>
 
   <body id="errorPage" class="blockedsite" dir="&locale.dir;">
 
     <div id="errorPageContainer">
 
       <!-- Error Title -->
       <div id="errorTitle">
         <h1 id="errorTitleText_phishing" class="errorTitleText">&safeb.blocked.phishingPage.title2;</h1>
         <h1 id="errorTitleText_malware" class="errorTitleText">&safeb.blocked.malwarePage.title;</h1>
         <h1 id="errorTitleText_unwanted" class="errorTitleText">&safeb.blocked.unwantedPage.title;</h1>
+        <h1 id="errorTitleText_forbidden" class="errorTitleText">&safeb.blocked.forbiddenPage.title;</h1>
       </div>
 
       <div id="errorLongContent">
       
         <!-- Short Description -->
         <div id="errorShortDesc">
           <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc2;</p>
           <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
           <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
+          <p id="errorShortDescText_forbidden">&safeb.blocked.forbiddenPage.shortDesc;</p>
         </div>
 
         <!-- Long Description -->
         <div id="errorLongDesc">
           <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc2;</p>
           <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
           <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
         </div>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -5282,16 +5282,18 @@ var ErrorPageEventHandler = {
           // The event came from a button on a malware/phishing block page
           // First check whether it's malware, phishing or unwanted, so that we
           // can use the right strings/links
           let bucketName = "WARNING_PHISHING_PAGE_";
           if (errorDoc.documentURI.contains("e=malwareBlocked")) {
             bucketName = "WARNING_MALWARE_PAGE_";
           } else if (errorDoc.documentURI.contains("e=unwantedBlocked")) {
             bucketName = "WARNING_UNWANTED_PAGE_";
+          } else if (errorDoc.documentURI.contains("e=forbiddenBlocked")) {
+            return; // no telemetry for forbidden pages
           }
           let nsISecTel = Ci.nsISecurityUITelemetry;
           let isIframe = (errorDoc.defaultView.parent === errorDoc.defaultView);
           bucketName += isIframe ? "TOP_" : "FRAME_";
 
           let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
 
           if (target == errorDoc.getElementById("getMeOutButton")) {
--- a/mobile/locales/en-US/overrides/appstrings.properties
+++ b/mobile/locales/en-US/overrides/appstrings.properties
@@ -27,13 +27,14 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
+forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4824,23 +4824,27 @@ 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,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");
 
 // 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");
 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");
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -48,16 +48,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");
 
 this.SafeBrowsing = {
 
   init: function() {
     if (this.initialized) {
       log("Already initialized");
       return;
     }
@@ -97,22 +98,27 @@ this.SafeBrowsing = {
       this.registerTableWithURLs(downloadAllowLists[i]);
     }
     for (let i = 0; i < trackingProtectionLists.length; ++i) {
       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]);
+    }
   },
 
 
-  initialized:     false,
-  phishingEnabled: false,
-  malwareEnabled:  false,
+  initialized:      false,
+  phishingEnabled:  false,
+  malwareEnabled:   false,
+  trackingEnabled:  false,
+  forbiddenEnabled: false,
 
   updateURL:             null,
   gethashURL:            null,
 
   reportURL:             null,
 
   getReportURL: function(kind, URI) {
     let pref;
@@ -148,16 +154,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.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();
@@ -223,17 +230,18 @@ this.SafeBrowsing = {
       } else {
         log("Update URL given but no lists managed for provider: " + provider);
       }
     }, this);
   },
 
   controlUpdateChecking: function() {
     log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:",
-        this.malwareEnabled, "trackingEnabled:", this.trackingEnabled);
+        this.malwareEnabled, "trackingEnabled:", this.trackingEnabled,
+       "forbiddenEnabled:", this.forbiddenEnabled);
 
     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 {
@@ -265,31 +273,39 @@ this.SafeBrowsing = {
       if (this.trackingEnabled) {
         listManager.enableUpdate(trackingProtectionLists[i]);
         listManager.enableUpdate(trackingProtectionWhitelists[i]);
       } else {
         listManager.disableUpdate(trackingProtectionLists[i]);
         listManager.disableUpdate(trackingProtectionWhitelists[i]);
       }
     }
+    for (let i = 0; i < forbiddenLists.length; ++i) {
+      if (this.forbiddenEnabled) {
+        listManager.enableUpdate(forbiddenLists[i]);
+      } else {
+        listManager.disableUpdate(forbiddenLists[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 forbiddenURL  = "itisatrap.org/firefox/forbidden.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" +
@@ -299,31 +315,34 @@ this.SafeBrowsing = {
               "ad:" + trackerURLs.length + "\n";
     trackerURLs.forEach((trackerURL, i) => {
       update += "a:" + (i + 1) + ":32:" + trackerURL.length + "\n" +
                 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;
     log("addMozEntries:", update);
 
     let db = Cc["@mozilla.org/url-classifier/dbservice;1"].
              getService(Ci.nsIUrlClassifierDBService);
 
     // nsIUrlClassifierUpdateObserver
     let dummyListener = {
       updateUrlRequested: function() { },
       streamFinished:     function() { },
       updateError:        function() { },
       updateSuccess:      function() { }
     };
 
     try {
-      let tables = "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple";
+      let tables = "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-forbid-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
@@ -67,24 +67,28 @@ PRLogModuleInfo *gUrlClassifierDbService
 #define CHECK_PHISHING_DEFAULT  false
 
 #define CHECK_TRACKING_PREF     "privacy.trackingprotection.enabled"
 #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 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 CONFIRM_AGE_PREF        "urlclassifier.max-complete-age"
 #define CONFIRM_AGE_DEFAULT_SEC (45 * 60)
 
 class nsUrlClassifierDBServiceWorker;
@@ -182,16 +186,19 @@ TablesToResponse(const nsACString& table
     return NS_ERROR_PHISHING_URI;
   }
   if (FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
     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;
+  }
   return NS_OK;
 }
 
 static nsCString
 ProcessLookupResults(LookupResultArray* results)
 {
   // Build a stringified list of result tables.
   nsTArray<nsCString> tables;
@@ -996,17 +1003,18 @@ class nsUrlClassifierClassifyCallback fi
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIURLCLASSIFIERCALLBACK
 
   nsUrlClassifierClassifyCallback(nsIURIClassifierCallback *c,
                                   bool checkMalware,
                                   bool checkPhishing,
-                                  bool checkTracking)
+                                  bool checkTracking,
+                                  bool checkForbidden)
     : mCallback(c)
     {}
 
 private:
   ~nsUrlClassifierClassifyCallback() {}
 
   nsCOMPtr<nsIURIClassifierCallback> mCallback;
 };
@@ -1056,16 +1064,17 @@ nsUrlClassifierDBService::GetInstance(ns
   return sUrlClassifierDBService;
 }
 
 
 nsUrlClassifierDBService::nsUrlClassifierDBService()
  : mCheckMalware(CHECK_MALWARE_DEFAULT)
  , mCheckPhishing(CHECK_PHISHING_DEFAULT)
  , mCheckTracking(CHECK_TRACKING_DEFAULT)
+ , mCheckForbiddenURIs(CHECK_FORBIDDEN_DEFAULT)
  , mInUpdate(false)
 {
 }
 
 nsUrlClassifierDBService::~nsUrlClassifierDBService()
 {
   sUrlClassifierDBService = nullptr;
 }
@@ -1102,16 +1111,22 @@ nsUrlClassifierDBService::ReadTablesFrom
   }
 
   Preferences::GetCString(TRACKING_WHITELIST_TABLE_PREF, &tables);
   if (!tables.IsEmpty()) {
     allTables.Append(',');
     allTables.Append(tables);
   }
 
+  Preferences::GetCString(FORBIDDEN_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;
 }
 
@@ -1133,33 +1148,37 @@ nsUrlClassifierDBService::Init()
   // Retrieve all the preferences.
   mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
     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);
   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, 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, 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);
@@ -1234,40 +1253,47 @@ nsUrlClassifierDBService::BuildTables(bo
       tables.Append(tracking);
     }
     Preferences::GetCString(TRACKING_WHITELIST_TABLE_PREF, &trackingWhitelist);
     if (!trackingWhitelist.IsEmpty()) {
       tables.Append(',');
       tables.Append(trackingWhitelist);
     }
   }
+  nsAutoCString forbidden;
+  Preferences::GetCString(FORBIDDEN_TABLE_PREF, &forbidden);
+  if (mCheckForbiddenURIs && !forbidden.IsEmpty()) {
+    tables.Append(',');
+    tables.Append(forbidden);
+  }
 
   if (StringBeginsWith(tables, NS_LITERAL_CSTRING(","))) {
     tables.Cut(0, 1);
   }
 }
 
 // nsChannelClassifier is the only consumer of this interface.
 NS_IMETHODIMP
 nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
                                    bool aTrackingProtectionEnabled,
                                    nsIURIClassifierCallback* c,
                                    bool* result)
 {
   NS_ENSURE_ARG(aPrincipal);
   NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
 
-  if (!(mCheckMalware || mCheckPhishing || aTrackingProtectionEnabled)) {
+  if (!(mCheckMalware || mCheckPhishing || aTrackingProtectionEnabled ||
+        mCheckForbiddenURIs)) {
     *result = false;
     return NS_OK;
   }
 
   RefPtr<nsUrlClassifierClassifyCallback> callback =
     new nsUrlClassifierClassifyCallback(c, mCheckMalware, mCheckPhishing,
-                                        mCheckTracking);
+                                        mCheckTracking, mCheckForbiddenURIs);
   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;
@@ -1559,21 +1585,25 @@ nsUrlClassifierDBService::Observe(nsISup
     } else if (NS_LITERAL_STRING(CHECK_PHISHING_PREF).Equals(aData)) {
       mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
         CHECK_PHISHING_DEFAULT);
     } 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(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(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);
@@ -1600,20 +1630,22 @@ nsUrlClassifierDBService::Shutdown()
   mCompleters.Clear();
 
   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(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(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
@@ -113,16 +113,20 @@ private:
   // TRUE if the nsURIClassifier implementation should check for phishing
   // uris on document loads.
   bool mCheckPhishing;
 
   // 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 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/webapprt/locales/en-US/webapprt/overrides/appstrings.properties
+++ b/webapprt/locales/en-US/webapprt/overrides/appstrings.properties
@@ -26,13 +26,14 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
+forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This application tried to access a resource that has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The application cannot continue loading because an error in the data transmission was detected.
 remoteXUL=This application tried to use an unsupported technology that is no longer available.
 sslv3Used=This application cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, this application has not connected to this website.
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -701,16 +701,17 @@
 #define MODULE NS_ERROR_MODULE_URILOADER
   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)),
   /* 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