Bug 1147212 - Add support for goog-unwanted-shavar. r=gcp,r=matej,r=smaug
authorFrancois Marier <francois@mozilla.com>
Wed, 22 Apr 2015 21:01:37 +1200
changeset 240533 8b191f5f96870fb5d6d4d46c3dd62fc2f679c011
parent 240532 4b37a75cb4bfd35caa129266b289c8a3566c688c
child 240534 329dd852c06bd7b0182c0d5ad06a883b44b358f2
push id28636
push userkwierso@gmail.com
push dateThu, 23 Apr 2015 00:16:12 +0000
treeherdermozilla-central@a5af73b32ac8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgcp, matej, smaug
bugs1147212
milestone40.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 1147212 - Add support for goog-unwanted-shavar. r=gcp,r=matej,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/components/safebrowsing/content/test/browser_bug400731.js
browser/components/safebrowsing/content/test/head.js
browser/locales/en-US/chrome/browser/browser.properties
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
browser/metro/base/content/contenthandlers/Content.js
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/android/locales/en-US/chrome/phishing.dtd
mobile/locales/en-US/overrides/appstrings.properties
modules/libpref/init/all.js
security/manager/boot/public/nsISecurityUITelemetry.idl
toolkit/components/url-classifier/SafeBrowsing.jsm
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/tests/mochitest/classifierFrame.html
toolkit/components/url-classifier/tests/mochitest/mochitest.ini
toolkit/components/url-classifier/tests/mochitest/test_classifier.html
toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html
toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js
toolkit/components/url-classifier/tests/mochitest/workerFrame.html
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
toolkit/components/viewsource/content/viewSource.js
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
@@ -25,13 +25,14 @@ contentEncodingError=The page you are tr
 unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 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.
 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.
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -369,16 +369,17 @@
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <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_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>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
@@ -396,16 +397,17 @@
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <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_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>
     </div>
 
--- a/browser/base/content/blockedSite.xhtml
+++ b/browser/base/content/blockedSite.xhtml
@@ -74,55 +74,106 @@
         // Handoff to the appropriate initializer, based on error code
         switch (getErrorCode()) {
           case "malwareBlocked" :
             initPage_malware();
             break;
           case "phishingBlocked" :
             initPage_phishing();
             break;
+          case "unwantedBlocked" :
+            initPage_unwanted();
+            break;
         }
       }        
       
       /**
        * Initialize custom strings and functionality for blocked malware case
        */
       function initPage_malware()
       {
-        // Remove phishing strings
+        // Remove phishing and unwanted 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_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("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 strings
+        // 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")
                                  .innerHTML;
       }
     ]]></script>
     <style type="text/css">
       /* Style warning button to look like a small text link in the
@@ -156,30 +207,33 @@
 
   <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>
       </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>
         </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>
         
         <!-- Action buttons -->
         <div id="buttons">
           <!-- Commands handled in browser.js -->
           <button id="getMeOutButton">&safeb.palm.accept.label;</button>
           <button id="reportButton">&safeb.palm.reportPage.label;</button>
         </div>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2654,17 +2654,17 @@ let BrowserOnClick = {
   receiveMessage: function (msg) {
     switch (msg.name) {
       case "Browser:CertExceptionError":
         this.onAboutCertError(msg.target, msg.data.elementId,
                               msg.data.isTopFrame, msg.data.location,
                               msg.data.sslStatusAsString);
       break;
       case "Browser:SiteBlockedError":
-        this.onAboutBlocked(msg.data.elementId, msg.data.isMalware,
+        this.onAboutBlocked(msg.data.elementId, msg.data.reason,
                             msg.data.isTopFrame, msg.data.location);
       break;
       case "Browser:EnableOnlineMode":
         if (Services.io.offline) {
           // Reset network state and refresh the page.
           Services.io.offline = false;
           msg.target.reload();
         }
@@ -2838,57 +2838,48 @@ let BrowserOnClick = {
         if (isTopFrame) {
           secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
         }
         break;
 
     }
   },
 
-  onAboutBlocked: function (elementId, isMalware, isTopFrame, location) {
-    // Depending on what page we are displaying here (malware/phishing)
+  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 = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_";
+    let bucketName = "WARNING_PHISHING_PAGE_";
+    if (reason === 'malware') {
+      bucketName = "WARNING_MALWARE_PAGE_";
+    } else if (reason === 'unwanted') {
+      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"]);
         getMeOutOfHere();
         break;
 
       case "reportButton":
-        // This is the "Why is this site blocked" button.  For malware,
-        // we can fetch a site-specific report, for phishing, we redirect
-        // to the generic page describing phishing protection.
-
-        // We log even if malware/phishing info URL couldn't be found:
+        // 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 (isMalware) {
-          // Get the stop badware "why is this blocked" report url,
-          // append the current url, and go there.
-          try {
-            let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
-            reportURL += location;
-            gBrowser.loadURI(reportURL);
-          } catch (e) {
-            Components.utils.reportError("Couldn't get malware report URL: " + e);
-          }
-        }
-        else { // It's a phishing site, not malware
-          openHelpLink("phishing-malware", false, "current");
-        }
+        openHelpLink("phishing-malware", false, "current");
         break;
 
       case "ignoreWarningButton":
         secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
-        this.ignoreWarningButton(isMalware);
+        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
    * the next page also in the parent) and instructs the browser to open the url
@@ -2905,17 +2896,17 @@ let BrowserOnClick = {
     if (anchorTarget instanceof HTMLAnchorElement &&
         anchorTarget.classList.contains("newtab-link")) {
       event.preventDefault();
       let where = whereToOpenLink(event, false, false);
       openLinkIn(anchorTarget.href, where, { charset: ownerDoc.characterSet });
     }
   },
 
-  ignoreWarningButton: function (isMalware) {
+  ignoreWarningButton: function (reason) {
     // Allow users to override and continue through to the site,
     // but add a notify bar as a reminder, so that they don't lose
     // track after, e.g., tab switching.
     gBrowser.loadURIWithFlags(gBrowser.currentURI.spec,
                               nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                               null, null, null);
 
     Services.perms.add(gBrowser.currentURI, "safe-browsing",
@@ -2924,34 +2915,38 @@ let BrowserOnClick = {
 
     let buttons = [{
       label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
       accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
       callback: function() { getMeOutOfHere(); }
     }];
 
     let title;
-    if (isMalware) {
+    if (reason === 'malware') {
       title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
       buttons[1] = {
         label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
         accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
         callback: function() {
           openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
         }
       };
-    } else {
+    } else if (reason === 'phishing') {
       title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
       buttons[1] = {
         label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
         accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
         callback: function() {
           openUILinkIn(gSafeBrowsing.getReportURL('Error'), '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.
     }
 
     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
@@ -379,19 +379,25 @@ let ClickEventHandler = {
       location: ownerDoc.location.href,
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
       sslStatusAsString: serializedSSLStatus
     });
   },
 
   onAboutBlocked: function (targetElement, ownerDoc) {
+    var reason = 'phishing';
+    if (/e=malwareBlocked/.test(ownerDoc.documentURI)) {
+      reason = 'malware';
+    } else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
+      reason = 'unwanted';
+    }
     sendAsyncMessage("Browser:SiteBlockedError", {
       location: ownerDoc.location.href,
-      isMalware: /e=malwareBlocked/.test(ownerDoc.documentURI),
+      reason: reason,
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
     });
   },
 
   onAboutNetError: function (event, documentURI) {
     let elmId = event.originalTarget.getAttribute("id");
     if (elmId != "errorTryAgain" || !/e=netOffline/.test(documentURI)) {
--- a/browser/components/safebrowsing/content/test/browser_bug400731.js
+++ b/browser/components/safebrowsing/content/test/browser_bug400731.js
@@ -29,16 +29,35 @@ function testMalware(event) {
 
   // Confirm that "Ignore this warning" is visible - bug 422410
   var el = content.document.getElementById("ignoreWarningButton");
   ok(el, "Ignore warning button should be present for malware");
   
   var style = content.getComputedStyle(el, null);
   is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for malware");
   
+  // Now launch the unwanted software test
+  window.addEventListener("DOMContentLoaded", testUnwanted, true);
+  content.location = "http://www.itisatrap.org/firefox/unwanted.html";
+}
+
+function testUnwanted(event) {
+  if (event.target != gBrowser.selectedBrowser.contentDocument) {
+    return;
+  }
+
+  window.removeEventListener("DOMContentLoaded", testUnwanted, true);
+
+  // Confirm that "Ignore this warning" is visible - bug 422410
+  var el = content.document.getElementById("ignoreWarningButton");
+  ok(el, "Ignore warning button should be present for unwanted software");
+
+  var style = content.getComputedStyle(el, null);
+  is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for unwanted software");
+
   // Now launch the phishing test
   window.addEventListener("DOMContentLoaded", testPhishing, true);
   content.location = "http://www.itisatrap.org/firefox/its-a-trap.html";
 }
 
 function testPhishing(event) {
   if (event.target != gBrowser.selectedBrowser.contentDocument) {
     return;
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -1,5 +1,5 @@
 // Force SafeBrowsing to be initialized for the tests
-Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple");
+Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
 Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
 SafeBrowsing.init();
 
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -393,16 +393,17 @@ pointerLock.autoLock.title2=%S will hide
 safebrowsing.getMeOutOfHereButton.label=Get me out of here!
 safebrowsing.getMeOutOfHereButton.accessKey=G
 safebrowsing.reportedWebForgery=Reported Web Forgery!
 safebrowsing.notAForgeryButton.label=This isn't a web forgery…
 safebrowsing.notAForgeryButton.accessKey=F
 safebrowsing.reportedAttackSite=Reported Attack Site!
 safebrowsing.notAnAttackButton.label=This isn't an attack site…
 safebrowsing.notAnAttackButton.accessKey=A
+safebrowsing.reportedUnwantedSite=Reported Unwanted Software Site!
 
 # Ctrl-Tab
 # LOCALIZATION NOTE (ctrlTab.listAllTabs.label): #1 represents the number
 # of tabs in the current browser window. It will always be 2 at least.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 ctrlTab.listAllTabs.label=;List All #1 Tabs
 
 # LOCALIZATION NOTE (addKeywordTitleAutoFill): %S will be replaced by the page's title
--- 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
@@ -7,12 +7,17 @@
 <!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) -->
 <!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) -->
+<!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) -->
 <!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>">
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -25,14 +25,15 @@ contentEncodingError=The page you are tr
 unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 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.
 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.
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -159,16 +159,21 @@ be temporary, and you can try again late
 ">
 
 <!ENTITY malwareBlocked.title "Suspected Attack Site!">
 <!ENTITY malwareBlocked.longDesc "
 <p>Attack sites try to install programs that steal private information, use your computer to attack others, or damage your system.</p>
 <p>Website owners who believe their site has been reported as an attack site in error may <a href='http://www.stopbadware.org/home/reviewinfo' >request a review</a>.</p>
 ">
 
+<!ENTITY unwantedBlocked.title "Suspected Unwanted Software Site!">
+<!ENTITY unwantedBlocked.longDesc "
+<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>
+">
+
 <!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 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>">
--- a/browser/metro/base/content/contenthandlers/Content.js
+++ b/browser/metro/base/content/contenthandlers/Content.js
@@ -336,30 +336,25 @@ let Content = {
         sendAsyncMessage("Browser:CertException",
                          { url: errorDoc.location.href, action: action });
       } else if (ot == errorDoc.getElementById("getMeOutOfHereButton")) {
         sendAsyncMessage("Browser:CertException",
                          { url: errorDoc.location.href, action: "leave" });
       }
     } else if (/^about:blocked/.test(errorDoc.documentURI)) {
       // The event came from a button on a malware/phishing block page
-      // First check whether it's malware or phishing, so that we can
-      // use the right strings/links.
-      let isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
     
       if (ot == errorDoc.getElementById("getMeOutButton")) {
         sendAsyncMessage("Browser:BlockedSite",
                          { url: errorDoc.location.href, action: "leave" });
       } else if (ot == errorDoc.getElementById("reportButton")) {
-        // This is the "Why is this site blocked" button.  For malware,
-        // we can fetch a site-specific report, for phishing, we redirect
-        // to the generic page describing phishing protection.
-        let action = isMalware ? "report-malware" : "report-phishing";
+        // This is the "Why is this site blocked" button. We redirect
+        // to the generic page describing phishing/malware protection.
         sendAsyncMessage("Browser:BlockedSite",
-                         { url: errorDoc.location.href, action: action });
+                         { url: errorDoc.location.href, action: "report-phishing" });
       } else if (ot == errorDoc.getElementById("ignoreWarningButton")) {
         // Allow users to override and continue through to the site,
         // but add a notify bar as a reminder, so that they don't lose
         // track after, e.g., tab switching.
         let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
         webNav.loadURI(content.location,
                        Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                        null, null, null);
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5082,17 +5082,18 @@ nsDocShell::DisplayLoadError(nsresult aE
           Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, bucketId);
         }
 
       } else {
         error.AssignLiteral("nssFailure2");
       }
     }
   } else if (NS_ERROR_PHISHING_URI == aError ||
-             NS_ERROR_MALWARE_URI == aError) {
+             NS_ERROR_MALWARE_URI == aError ||
+             NS_ERROR_UNWANTED_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 =
@@ -5101,24 +5102,29 @@ nsDocShell::DisplayLoadError(nsresult aE
       errorPage.Assign(alternateErrorPage);
     }
 
     uint32_t bucketId;
     if (NS_ERROR_PHISHING_URI == aError) {
       error.AssignLiteral("phishingBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP;
-    } else {
+    } else if (NS_ERROR_MALWARE_URI == aError) {
       error.AssignLiteral("malwareBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP;
-    }
-
-    if (errorPage.EqualsIgnoreCase("blocked"))
+    } else {
+      error.AssignLiteral("unwantedBlocked");
+      bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME
+                           : nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP;
+    }
+
+    if (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");
 
     nsCOMPtr<EventTarget> handler = mChromeEventHandler;
     if (handler) {
@@ -7819,16 +7825,17 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
     else if (aStatus == NS_ERROR_NET_TIMEOUT ||
              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_UNSAFE_CONTENT_TYPE ||
              aStatus == NS_ERROR_REMOTE_XUL ||
              aStatus == NS_ERROR_OFFLINE ||
              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
@@ -286,16 +286,17 @@
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <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_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>
@@ -312,16 +313,17 @@
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <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_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
@@ -1287,16 +1287,19 @@ BrowserElementChild.prototype = {
             sendAsyncMsg('error', { type: 'cspBlocked' });
             return;
           case Cr.NS_ERROR_PHISHING_URI :
             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_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
@@ -24,13 +24,14 @@ contentEncodingError=The page you are tr
 unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 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.
 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.
--- a/dom/locales/en-US/chrome/netError.dtd
+++ b/dom/locales/en-US/chrome/netError.dtd
@@ -76,16 +76,21 @@
 ">
 
 <!ENTITY malwareBlocked.title "Suspected Attack Site!">
 <!ENTITY malwareBlocked.longDesc "
 <p>Attack sites try to install programs that steal private information, use your computer to attack others, or damage your system.</p>
 <p>Website owners who believe their site has been reported as an attack site in error may <a href='http://www.stopbadware.org/home/reviewinfo' >request a review</a>.</p>
 ">
 
+<!ENTITY unwantedBlocked.title "Suspected Unwanted Software Site!">
+<!ENTITY unwantedBlocked.longDesc "
+<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>
+">
+
 <!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 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
@@ -75,83 +75,129 @@
         // Handoff to the appropriate initializer, based on error code
         switch (getErrorCode()) {
           case "malwareBlocked" :
             initPage_malware();
             break;
           case "phishingBlocked" :
             initPage_phishing();
             break;
+          case "unwantedBlocked" :
+            initPage_unwanted();
+            break;
         }
       }        
       
       /**
        * Initialize custom strings and functionality for blocked malware case
        */
       function initPage_malware()
       {
-        // Remove phishing strings
+        // Remove phishing/unwanted strings
         var el = document.getElementById("errorTitleText_phishing");
         el.parentNode.removeChild(el);
+        el = document.getElementById("errorTitleText_unwanted");
+        el.parentNode.removeChild(el);
 
         el = document.getElementById("errorShortDescText_phishing");
         el.parentNode.removeChild(el);
+        el = document.getElementById("errorShortDescText_unwanted");
+        el.parentNode.removeChild(el);
 
         el = document.getElementById("errorLongDescText_phishing");
         el.parentNode.removeChild(el);
+        el = document.getElementById("errorLongDescText_unwanted");
+        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 strings
+        // 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")
+                                 .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>
       </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>
         </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>
         
         <!-- Action buttons -->
         <div id="buttons">
           <!-- Commands handled in browser.js -->
           <button id="getMeOutButton">&safeb.palm.accept.label;</button>
           <button id="reportButton">&safeb.palm.reportPage.label;</button>
         </div>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -5542,51 +5542,42 @@ var ErrorPageEventHandler = {
               dump("Failed to set cert exception: " + e + "\n");
             }
             errorDoc.location.reload();
           } else if (target == errorDoc.getElementById("getMeOutOfHereButton")) {
             errorDoc.location = "about:home";
           }
         } else if (errorDoc.documentURI.startsWith("about:blocked")) {
           // The event came from a button on a malware/phishing block page
-          // First check whether it's malware or phishing, so that we can
-          // use the right strings/links
-          let isMalware = errorDoc.documentURI.contains("e=malwareBlocked");
-          let bucketName = isMalware ? "WARNING_MALWARE_PAGE_" : "WARNING_PHISHING_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_";
+          }
           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")) {
             Telemetry.addData("SECURITY_UI", nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
             errorDoc.location = "about:home";
           } else if (target == errorDoc.getElementById("reportButton")) {
             // We log even if malware/phishing info URL couldn't be found:
             // the measurement is for how many users clicked the WHY BLOCKED button
             Telemetry.addData("SECURITY_UI", nsISecTel[bucketName + "WHY_BLOCKED"]);
 
-            // This is the "Why is this site blocked" button.  For malware,
-            // we can fetch a site-specific report, for phishing, we redirect
-            // to the generic page describing phishing protection.
-            if (isMalware) {
-              // Get the stop badware "why is this blocked" report url, append the current url, and go there.
-              try {
-                let reportURL = formatter.formatURLPref("browser.safebrowsing.malware.reportURL");
-                reportURL += errorDoc.location.href;
-                BrowserApp.selectedBrowser.loadURI(reportURL);
-              } catch (e) {
-                Cu.reportError("Couldn't get malware report URL: " + e);
-              }
-            } else {
-              // It's a phishing site, just link to the generic information page
-              let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
-              BrowserApp.selectedBrowser.loadURI(url + "phishing-malware");
-            }
+            // This is the "Why is this site blocked" button. We redirect
+            // to the generic page describing phishing/malware protection.
+            let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+            BrowserApp.selectedBrowser.loadURI(url + "phishing-malware");
           } else if (target == errorDoc.getElementById("ignoreWarningButton")) {
             Telemetry.addData("SECURITY_UI", nsISecTel[bucketName + "IGNORE_WARNING"]);
 
             // Allow users to override and continue through to the site,
             let webNav = BrowserApp.selectedBrowser.docShell.QueryInterface(Ci.nsIWebNavigation);
             let location = BrowserApp.selectedBrowser.contentWindow.location;
             webNav.loadURI(location, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, null, null, null);
 
--- a/mobile/android/locales/en-US/chrome/phishing.dtd
+++ b/mobile/android/locales/en-US/chrome/phishing.dtd
@@ -10,8 +10,13 @@
 <!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) -->
 <!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.phishingPage.title2 "Suspected Web Forgery!">
 <!ENTITY safeb.blocked.phishingPage.shortDesc2 "Entering any personal information on this page may result in identity theft or other fraud.">
 <!ENTITY safeb.blocked.phishingPage.longDesc2 "<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 safeb.blocked.unwantedPage.title "Reported Unwanted Software Site!">
+<!-- Localization note (safeb.blocked.unwanted.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 "Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.">
--- a/mobile/locales/en-US/overrides/appstrings.properties
+++ b/mobile/locales/en-US/overrides/appstrings.properties
@@ -26,12 +26,13 @@ unsafeContentType=The page you are tryin
 externalProtocolTitle=External Protocol Request
 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.
 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.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4472,21 +4472,21 @@ pref("dom.broadcastChannel.enabled", tru
 
 // DOM Inter-App Communication API.
 pref("dom.inter-app-communication-api.enabled", false);
 
 // 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,test-malware-simple");
+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,goog-downloadwhite-digest256,mozpub-track-digest256");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,goog-downloadwhite-digest256,mozpub-track-digest256");
 
 // The table and update/gethash URLs for Safebrowsing phishing and malware
 // checks.
 pref("urlclassifier.trackingTable", "mozpub-track-digest256");
 pref("browser.trackingprotection.updateURL", "https://tracking.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.trackingprotection.gethashURL", "https://tracking.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 
 // Turn off Spatial navigation by default.
--- a/security/manager/boot/public/nsISecurityUITelemetry.idl
+++ b/security/manager/boot/public/nsISecurityUITelemetry.idl
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(f7259bf4-1f2b-4e9e-8983-1978cc076fa1)]
+[scriptable, uuid(56e190a0-2802-4fc4-b09f-bcda357035c3)]
 
 interface nsISecurityUITelemetry : nsISupports {
 
 /*
  * Addon installation warnings
  */
 
 // Firefox prevented this site from asking you to install addon
@@ -136,11 +136,22 @@ const uint32_t WARNING_BAD_CERT_TOP_ADD_
 const uint32_t WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_UNTRUSTED = 1;
 const uint32_t WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_DOMAIN = 2;
 const uint32_t WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_TIME = 4;
 
 const uint32_t WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_BASE = 84;
 const uint32_t WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTED = 1;
 const uint32_t WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_DOMAIN = 2;
 const uint32_t WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_TIME = 4;
-// This uses up buckets till 91 (including)
+
+// Another Safe Browsing list (like malware & phishing above)
+const uint32_t WARNING_UNWANTED_PAGE_TOP = 92;
+const uint32_t WARNING_UNWANTED_PAGE_TOP_WHY_BLOCKED = 93;
+const uint32_t WARNING_UNWANTED_PAGE_TOP_GET_ME_OUT_OF_HERE = 94;
+const uint32_t WARNING_UNWANTED_PAGE_TOP_IGNORE_WARNING = 95;
+const uint32_t WARNING_UNWANTED_PAGE_FRAME = 96;
+const uint32_t WARNING_UNWANTED_PAGE_FRAME_WHY_BLOCKED = 97;
+const uint32_t WARNING_UNWANTED_PAGE_FRAME_GET_ME_OUT_OF_HERE = 98;
+const uint32_t WARNING_UNWANTED_PAGE_FRAME_IGNORE_WARNING = 99;
+
+// This uses up buckets till 99 (including)
 // We only have buckets up to 100.
 };
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -194,40 +194,44 @@ this.SafeBrowsing = {
     }
     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 phishURL    = "itisatrap.org/firefox/its-a-trap.html";
+    const malwareURL  = "itisatrap.org/firefox/its-an-attack.html";
+    const unwantedURL = "itisatrap.org/firefox/unwanted.html";
 
     let update = "n:1000\ni:test-malware-simple\nad:1\n" +
                  "a:1:32:" + malwareURL.length + "\n" +
                  malwareURL;
     update += "n:1000\ni:test-phish-simple\nad:1\n" +
               "a:1:32:" + phishURL.length + "\n" +
               phishURL;
+    update += "n:1000\ni:test-unwanted-simple\nad:1\n" +
+              "a:1:32:" + unwantedURL.length + "\n" +
+              unwantedURL;
     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 {
-      db.beginUpdate(dummyListener, "test-malware-simple,test-phish-simple", "");
+      db.beginUpdate(dummyListener, "test-malware-simple,test-phish-simple,test-unwanted-simple", "");
       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
@@ -183,16 +183,19 @@ TablesToResponse(const nsACString& table
     return NS_ERROR_MALWARE_URI;
   }
   if (FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
     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;
+  }
   return NS_OK;
 }
 
 static nsCString
 ProcessLookupResults(LookupResultArray* results)
 {
   // Build a stringified list of result tables.
   nsTArray<nsCString> tables;
--- a/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html
+++ b/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html
@@ -28,18 +28,18 @@ function checkLoads() {
   window.parent.SimpleTest.finish();
 }
 
 </script>
 
 <!-- Try loading from a malware javascript URI -->
 <script type="text/javascript" src="http://malware.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"></script>
 
-<!-- Try loading from a malware css URI -->
-<link rel="stylesheet" type="text/css" href="http://malware.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link>
+<!-- Try loading from an uwanted software css URI -->
+<link rel="stylesheet" type="text/css" href="http://unwanted.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link>
 
 <!-- XXX How is this part of the test supposed to work (= be checked)? -->
 <!-- Try loading a marked-as-malware css through an @import from a clean URI -->
 <link rel="stylesheet" type="text/css" href="import.css"></link>
 </head>
 
 <body onload="checkLoads()">
 The following should not be hidden:
--- a/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
@@ -4,12 +4,13 @@ support-files =
   classifierFrame.html
   cleanWorker.js
   evil.css
   evil.js
   evilWorker.js
   import.css
   raptor.jpg
   track.html
+  unwantedWorker.js
   workerFrame.html
 
 [test_classifier.html]
 [test_classifier_worker.html]
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
@@ -19,16 +19,22 @@ var firstLoad = true;
 
 // Add some URLs to the malware database.
 var testData = "malware.example.com/";
 var testUpdate =
   "n:1000\ni:test-malware-simple\nad:1\n" +
   "a:524:32:" + testData.length + "\n" +
   testData;
 
+testData = "unwanted.example.com/";
+testUpdate +=
+  "n:1000\ni:test-unwanted-simple\nad:1\n" +
+  "a:524:32:" + testData.length + "\n" +
+  testData;
+
 var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
                 .getService(Ci.nsIUrlClassifierDBService);
 
 function loadTestFrame() {
   document.getElementById("testFrame").src = "classifierFrame.html";
 }
 
 function doUpdate(update) {
@@ -50,25 +56,25 @@ function doUpdate(update) {
     },
     updateSuccess: function(requestedTimeout) {
       SpecialPowers.pushPrefEnv(
         {"set" : [["browser.safebrowsing.malware.enabled", true]]},
         loadTestFrame);
     }
   };
 
-  dbService.beginUpdate(listener, "test-malware-simple", "");
+  dbService.beginUpdate(listener, "test-malware-simple,test-unwanted-simple", "");
   dbService.beginStream("", "");
   dbService.updateStream(update);
   dbService.finishStream();
   dbService.finishUpdate();
 }
 
 SpecialPowers.pushPrefEnv(
-  {"set" : [["urlclassifier.malwareTable", "test-malware-simple"],
+  {"set" : [["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"],
             ["urlclassifier.phishTable", "test-phish-simple"]]},
   function() { doUpdate(testUpdate); });
 
 // Expected finish() call is in "classifierFrame.html".
 SimpleTest.waitForExplicitFinish();
 
 </script>
 
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html
@@ -18,16 +18,22 @@ var Ci = SpecialPowers.Ci;
 
 // Add some URLs to the malware database.
 var testData = "example.com/tests/toolkit/components/url-classifier/tests/mochitest/evilWorker.js";
 var testUpdate =
   "n:1000\ni:test-malware-simple\nad:550\n" +
   "a:550:32:" + testData.length + "\n" +
   testData;
 
+testData = "example.com/tests/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js";
+testUpdate +=
+  "n:1000\ni:test-unwanted-simple\nad:550\n" +
+  "a:550:32:" + testData.length + "\n" +
+  testData;
+
 var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
                 .getService(Ci.nsIUrlClassifierDBService);
 
 function doUpdate(update) {
   var listener = {
     QueryInterface: SpecialPowers.wrapCallback(function(iid)
     {
       if (iid.equals(Ci.nsISupports) ||
@@ -49,17 +55,17 @@ function doUpdate(update) {
         function loadTestFrame() {
           document.getElementById("testFrame").src =
             "http://example.com/tests/toolkit/components/url-classifier/tests/mochitest/workerFrame.html";
         }
       );
     }
   };
 
-  dbService.beginUpdate(listener, "test-malware-simple", "");
+  dbService.beginUpdate(listener, "test-malware-simple,test-unwanted-simple", "");
   dbService.beginStream("", "");
   dbService.updateStream(update);
   dbService.finishStream();
   dbService.finishUpdate();
 }
 
 function onmessage(event)
 {
@@ -68,17 +74,17 @@ function onmessage(event)
     SimpleTest.finish();
     return;
   }
 
   is(pieces[0], "success", pieces[1]);
 }
 
 SpecialPowers.pushPrefEnv(
-  {"set" : [["urlclassifier.malwareTable", "test-malware-simple"],
+  {"set" : [["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"],
             ["urlclassifier.phishTable", "test-phish-simple"]]},
   function() { doUpdate(testUpdate); });
 
 window.addEventListener("message", onmessage, false);
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
copy from toolkit/components/url-classifier/tests/mochitest/evilWorker.js
copy to toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js
--- a/toolkit/components/url-classifier/tests/mochitest/workerFrame.html
+++ b/toolkit/components/url-classifier/tests/mochitest/workerFrame.html
@@ -24,21 +24,37 @@ function startCleanWorker() {
   worker.postMessage("");
 }
 
 function startEvilWorker() {
   var worker = new Worker("evilWorker.js");
 
   worker.onmessage = function(event) {
     window.parent.postMessage("failure:failed to block evilWorker.js", "*");
+    startUnwantedWorker();
+  };
+
+  worker.onerror = function(event) {
+    window.parent.postMessage("success:blocked evilWorker.js", "*");
+    startUnwantedWorker();
+  };
+
+  worker.postMessage("");
+}
+
+function startUnwantedWorker() {
+  var worker = new Worker("unwantedWorker.js");
+
+  worker.onmessage = function(event) {
+    window.parent.postMessage("failure:failed to block unwantedWorker.js", "*");
     startCleanWorker();
   };
 
   worker.onerror = function(event) {
-    window.parent.postMessage("success:blocked evilWorker.js", "*");
+    window.parent.postMessage("success:blocked unwantedWorker.js", "*");
     startCleanWorker();
   };
 
   worker.postMessage("");
 }
 
 </script>
 
--- a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
+++ b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
@@ -48,25 +48,28 @@ 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-phish-simple.cache");
   delFile("safebrowsing/test-malware-simple.cache");
+  delFile("safebrowsing/test-unwanted-simple.cache");
   delFile("safebrowsing/test-phish-simple.pset");
   delFile("safebrowsing/test-malware-simple.pset");
+  delFile("safebrowsing/test-unwanted-simple.pset");
   delFile("testLarge.pset");
   delFile("testNoDelta.pset");
 }
 
-var allTables = "test-phish-simple,test-malware-simple";
+var allTables = "test-phish-simple,test-malware-simple,test-unwanted-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:
@@ -109,16 +112,20 @@ function buildUpdate(update, hashSize) {
 function buildPhishingUpdate(chunks, hashSize) {
   return buildUpdate({"test-phish-simple" : chunks}, hashSize);
 }
 
 function buildMalwareUpdate(chunks, hashSize) {
   return buildUpdate({"test-malware-simple" : chunks}, hashSize);
 }
 
+function buildUnwantedUpdate(chunks, hashSize) {
+  return buildUpdate({"test-unwanted-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) {
@@ -133,17 +140,17 @@ function doSimpleUpdate(updateText, succ
 
     updateUrlRequested: function(url) { },
     streamFinished: function(status) { },
     updateError: function(errorCode) { failure(errorCode); },
     updateSuccess: function(requestedTimeout) { success(requestedTimeout); }
   };
 
   dbservice.beginUpdate(listener,
-                        "test-phish-simple,test-malware-simple");
+                        "test-phish-simple,test-malware-simple,test-unwanted-simple");
   dbservice.beginStream("", "");
   dbservice.updateStream(updateText);
   dbservice.finishStream();
   dbservice.finishUpdate();
 }
 
 /**
  * Simulates a failed database update.
@@ -175,17 +182,17 @@ function doErrorUpdate(tables, success, 
  */
 function doStreamUpdate(updateText, success, failure, downloadFailure) {
   var dataUpdate = "data:," + encodeURIComponent(updateText);
 
   if (!downloadFailure) {
     downloadFailure = failure;
   }
 
-  streamUpdater.downloadUpdates("test-phish-simple,test-malware-simple", "",
+  streamUpdater.downloadUpdates("test-phish-simple,test-malware-simple,test-unwanted-simple", "",
                                 dataUpdate, success, failure, downloadFailure);
 }
 
 var gAssertions = {
 
 tableData : function(expectedTables, cb)
 {
   dbservice.getTables(function(tables) {
@@ -232,16 +239,21 @@ urlsExist: function(urls, cb)
   this.checkUrls(urls, 'test-phish-simple', cb);
 },
 
 malwareUrlsExist: function(urls, cb)
 {
   this.checkUrls(urls, 'test-malware-simple', cb);
 },
 
+unwantedUrlsExist: function(urls, cb)
+{
+  this.checkUrls(urls, 'test-unwanted-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,28 +42,30 @@ var chunk5Urls = [
 var chunk5 = chunk5Urls.join("\n");
 
 var chunk6Urls = [
   "h.com/i",
   "j.com/k",
   ];
 var chunk6 = chunk6Urls.join("\n");
 
-// we are going to add chunks 1, 2, 4, 5, and 6 to phish-simple, and
-// chunk 2 to malware-simple.  Then we'll remove the urls in chunk3
-// from phish-simple, then expire chunk 1 and chunks 4-6 from
-// phish-simple.
+// we are going to add chunks 1, 2, 4, 5, and 6 to phish-simple,
+// chunk 2 to malware-simple and chunk 3 to unwanted-simple.
+// Then we'll remove the urls in chunk3 from phish-simple, then
+// expire chunk 1 and chunks 4-6 from phish-simple.
 var phishExpected = {};
 var phishUnexpected = {};
 var malwareExpected = {};
+var unwantedExpected = {};
 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;
 }
 for (var i = 0; i < chunk1Urls.length; i++) {
   // chunk1 urls are expired
   phishUnexpected[chunk1Urls[i]] = true;
 }
 for (var i = 0; i < chunk4Urls.length; i++) {
@@ -110,17 +112,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-malware-simple;a:1\ntest-phish-simple;a:2");
+              "\ntest-malware-simple;a:1\ntest-phish-simple;a:2\ntest-unwanted-simple;a:1");
 
   checkNoHost();
 }
 
 
 function expireSubSuccess(result) {
   dbservice.getTables(tablesCallbackWithoutSub);
 }
@@ -128,17 +130,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-malware-simple;a:1\ntest-phish-simple;a:2:s:3");
+              "\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);
@@ -177,16 +179,26 @@ function malwareExists(result) {
 
   try {
     do_check_true(result.indexOf("test-malware-simple") != -1);
   } finally {
     checkDone();
   }
 }
 
+function unwantedExists(result) {
+  dumpn("unwantedExists: " + result);
+
+  try {
+    do_check_true(result.indexOf("test-unwanted-simple") != -1);
+  } finally {
+    checkDone();
+  }
+}
+
 function checkState()
 {
   numExpecting = 0;
 
   for (var key in phishExpected) {
     var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + key, null, null));
     dbservice.lookup(principal, allTables, phishExists, true);
     numExpecting++;
@@ -198,16 +210,22 @@ function checkState()
     numExpecting++;
   }
 
   for (var key in malwareExpected) {
     var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + key, null, null));
     dbservice.lookup(principal, allTables, malwareExists, true);
     numExpecting++;
   }
+
+  for (var key in unwantedExpected) {
+    var principal = secMan.getNoAppCodebasePrincipal(iosvc.newURI("http://" + key, null, null));
+    dbservice.lookup(principal, allTables, unwantedExists, true);
+    numExpecting++;
+  }
 }
 
 function testSubSuccess(result)
 {
   do_check_eq(result, "1000");
   checkState();
 }
 
@@ -244,17 +262,20 @@ function do_adds() {
     "a:4:32:" + chunk4.length + "\n" +
     chunk4 + "\n" +
     "a:5:32:" + chunk5.length + "\n" +
     chunk5 + "\n" +
     "a:6:32:" + chunk6.length + "\n" +
     chunk6 + "\n" +
     "i:test-malware-simple\n" +
     "a:1:32:" + chunk2.length + "\n" +
-      chunk2 + "\n";
+      chunk2 + "\n" +
+    "i:test-unwanted-simple\n" +
+    "a:1:32:" + chunk3.length + "\n" +
+      chunk3 + "\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
@@ -126,16 +126,17 @@ function testErrorUrlForward() {
 
   doTest([update], assertions, true);
 }
 
 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 update = "n:1000\n";
   update += "i:test-phish-simple\n";
 
   var update1 = buildBareUpdate(
     [{ "chunkNum" : 1,
        "urls" : add1Urls }]);
   update += "u:data:," + encodeURIComponent(update1) + "\n";
@@ -147,20 +148,27 @@ function testMultipleTables() {
 
   update += "i:test-malware-simple\n";
 
   var update3 = buildBareUpdate(
     [{ "chunkNum" : 3,
        "urls" : add3Urls }]);
   update += "u:data:," + encodeURIComponent(update3) + "\n";
 
+  update += "i:test-unwanted-simple\n";
+  var update4 = buildBareUpdate(
+    [{ "chunkNum" : 4,
+       "urls" : add4Urls }]);
+  update += "u:data:," + encodeURIComponent(update4) + "\n";
+
   var assertions = {
-    "tableData" : "test-malware-simple;a:3\ntest-phish-simple;a:1-2",
+    "tableData" : "test-malware-simple;a:3\ntest-phish-simple;a:1-2\ntest-unwanted-simple;a:4",
     "urlsExist" : add1Urls.concat(add2Urls),
-    "malwareUrlsExist" : add3Urls
+    "malwareUrlsExist" : add3Urls,
+    "unwantedUrlsExist" : add4Urls
   };
 
   doTest([update], assertions, false);
 }
 
 function Observer(callback) {
   this.observe = callback;
 }
--- a/toolkit/components/viewsource/content/viewSource.js
+++ b/toolkit/components/viewsource/content/viewSource.js
@@ -237,43 +237,25 @@ function onClickContent(event) {
   if (!event.isTrusted || event.target.localName != "button")
     return;
 
   var target = event.originalTarget;
   var errorDoc = target.ownerDocument;
 
   if (/^about:blocked/.test(errorDoc.documentURI)) {
     // The event came from a button on a malware/phishing block page
-    // First check whether it's malware or phishing, so that we can
-    // use the right strings/links
-    var isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
 
     if (target == errorDoc.getElementById('getMeOutButton')) {
       // Instead of loading some safe page, just close the window
       window.close();
     } else if (target == errorDoc.getElementById('reportButton')) {
-      // This is the "Why is this site blocked" button.  For malware,
-      // we can fetch a site-specific report, for phishing, we redirect
-      // to the generic page describing phishing protection.
-
-      if (isMalware) {
-        // Get the stop badware "why is this blocked" report url,
-        // append the current url, and go there.
-        try {
-          let reportURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.malware.reportURL", true);
-          reportURL += errorDoc.location.href.slice(12);
-          openURL(reportURL);
-        } catch (e) {
-          Components.utils.reportError("Couldn't get malware report URL: " + e);
-        }
-      } else {
-        // It's a phishing site, just link to the generic information page
-        let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
-        openURL(url + "phishing-malware");
-      }
+      // This is the "Why is this site blocked" button. We redirect
+      // to the generic page describing phishing/malware protection.
+      let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+      openURL(url + "phishing-malware");
     } else if (target == errorDoc.getElementById('ignoreWarningButton')) {
       // Allow users to override and continue through to the site
       gBrowser.loadURIWithFlags(content.location.href,
                                 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                                 null, null, null);
     }
   }
 }
--- a/webapprt/locales/en-US/webapprt/overrides/appstrings.properties
+++ b/webapprt/locales/en-US/webapprt/overrides/appstrings.properties
@@ -24,13 +24,14 @@ contentEncodingError=The application rec
 unsafeContentType=The application cannot continue because it accessed a file type that may not be safe to open. Please contact the application authors to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 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.
 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.
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -673,16 +673,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)),
   /* 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