Bug 1288633 - Add more information when an URL matches Safe Browsing list. r=dragana,francois
authorThomas Nguyen <tnguyen@mozilla.com>
Tue, 21 Feb 2017 09:46:36 +0800
changeset 344065 077f42a9964c78e0abd210749147468fd23f4e1f
parent 344064 2c1fc455e81e0c7744e38a36b6df8657f462a369
child 344066 c4eeea895a9cf776832263855a97b5bfe86a6d6e
push id87277
push userkwierso@gmail.com
push dateWed, 22 Feb 2017 01:41:20 +0000
treeherdermozilla-inbound@988dd3817ad4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana, francois
bugs1288633
milestone54.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 1288633 - Add more information when an URL matches Safe Browsing list. r=dragana,francois MozReview-Commit-ID: 6u0dUOB838F
browser/base/content/browser-safebrowsing.js
browser/base/content/browser.js
browser/base/content/content.js
dom/ipc/PURLClassifier.ipdl
dom/ipc/PURLClassifierInfo.ipdlh
dom/ipc/URLClassifierChild.cpp
dom/ipc/URLClassifierChild.h
dom/ipc/URLClassifierParent.cpp
dom/ipc/moz.build
modules/libpref/init/all.js
netwerk/base/moz.build
netwerk/base/nsChannelClassifier.cpp
netwerk/base/nsChannelClassifier.h
netwerk/base/nsIClassifiedChannel.idl
netwerk/base/nsIParentChannel.idl
netwerk/base/nsIURIClassifier.idl
netwerk/locales/en-US/necko.properties
netwerk/protocol/data/DataChannelParent.cpp
netwerk/protocol/ftp/FTPChannelParent.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/PHttpChannel.ipdl
toolkit/components/url-classifier/Entries.h
toolkit/components/url-classifier/SafeBrowsing.jsm
toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
uriloader/base/nsIWebProgressListener.idl
uriloader/exthandler/nsExternalProtocolHandler.cpp
--- a/browser/base/content/browser-safebrowsing.js
+++ b/browser/base/content/browser-safebrowsing.js
@@ -34,15 +34,32 @@ var gSafeBrowsing = {
     if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
       broadcaster.removeAttribute("disabled");
     else
       broadcaster.setAttribute("disabled", true);
   },
 
   /**
    * Used to report a phishing page or a false positive
-   * @param name String One of "Phish", "Error", "Malware" or "MalwareError"
+   *
+   * @param name
+   *        String One of "PhishMistake", "MalwareMistake", or "Phish"
+   * @param info
+   *        Information about the reasons for blocking the resource.
+   *        In the case false positive, it may contain SafeBrowsing
+   *        matching list and provider of the list
    * @return String the report phishing URL.
    */
-  getReportURL(name) {
-    return SafeBrowsing.getReportURL(name, gBrowser.currentURI);
+  getReportURL(name, info) {
+    let reportInfo = info;
+    if (!reportInfo) {
+      let pageUri = gBrowser.currentURI.clone();
+
+      // Remove the query to avoid including potentially sensitive data
+      if (pageUri instanceof Ci.nsIURL) {
+        pageUri.query = "";
+      }
+
+      reportInfo = { uri: pageUri.asciiSpec };
+    }
+    return SafeBrowsing.getReportURL(name, reportInfo);
   }
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2967,17 +2967,18 @@ var BrowserOnClick = {
                          msg.data.isTopFrame, msg.data.location,
                          msg.data.securityInfoAsString);
       break;
       case "Browser:OpenCaptivePortalPage":
         CaptivePortalWatcher.ensureCaptivePortalTab();
       break;
       case "Browser:SiteBlockedError":
         this.onAboutBlocked(msg.data.elementId, msg.data.reason,
-                            msg.data.isTopFrame, msg.data.location);
+                            msg.data.isTopFrame, msg.data.location,
+                            msg.data.blockedInfo);
       break;
       case "Browser:EnableOnlineMode":
         if (Services.io.offline) {
           // Reset network state and refresh the page.
           Services.io.offline = false;
           msg.target.reload();
         }
       break;
@@ -3092,17 +3093,17 @@ var BrowserOnClick = {
         let detailedInfo = getDetailedCertErrorInfo(location,
                                                     securityInfo);
         gClipboardHelper.copyString(detailedInfo);
         break;
 
     }
   },
 
-  onAboutBlocked(elementId, reason, isTopFrame, location) {
+  onAboutBlocked(elementId, reason, isTopFrame, location, blockedInfo) {
     // Depending on what page we are displaying here (malware/phishing/unwanted)
     // use the right strings and links for each.
     let bucketName = "";
     let sendTelemetry = false;
     if (reason === "malware") {
       sendTelemetry = true;
       bucketName = "WARNING_MALWARE_PAGE_";
     } else if (reason === "phishing") {
@@ -3135,23 +3136,23 @@ var BrowserOnClick = {
         openHelpLink("phishing-malware", false, "current");
         break;
 
       case "ignoreWarningButton":
         if (gPrefService.getBoolPref("browser.safebrowsing.allowOverride")) {
           if (sendTelemetry) {
             secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
           }
-          this.ignoreWarningButton(reason);
+          this.ignoreWarningButton(reason, blockedInfo);
         }
         break;
     }
   },
 
-  ignoreWarningButton(reason) {
+  ignoreWarningButton(reason, blockedInfo) {
     // 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",
@@ -3161,33 +3162,43 @@ var BrowserOnClick = {
     let buttons = [{
       label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
       accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
       callback() { getMeOutOfHere(); }
     }];
 
     let title;
     if (reason === "malware") {
-      title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
-      buttons[1] = {
-        label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
-        accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
-        callback() {
-          openUILinkIn(gSafeBrowsing.getReportURL("MalwareMistake"), "tab");
-        }
-      };
+      let reportUrl = gSafeBrowsing.getReportURL("MalwareMistake", blockedInfo);
+
+      // There's no button if we can not get report url, for example if the provider
+      // of blockedInfo is not Google
+      if (reportUrl) {
+        buttons[1] = {
+          label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
+          accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
+          callback() {
+            openUILinkIn(reportUrl, "tab");
+          }
+        };
+      }
     } else if (reason === "phishing") {
-      title = gNavigatorBundle.getString("safebrowsing.deceptiveSite");
-      buttons[1] = {
-        label: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.label"),
-        accessKey: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.accessKey"),
-        callback() {
-          openUILinkIn(gSafeBrowsing.getReportURL("PhishMistake"), "tab");
-        }
-      };
+      let reportUrl = gSafeBrowsing.getReportURL("PhishMistake", blockedInfo);
+
+      // There's no button if we can not get report url, for example if the provider
+      // of blockedInfo is not Google
+      if (reportUrl) {
+        buttons[1] = {
+          label: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.label"),
+          accessKey: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.accessKey"),
+          callback() {
+            openUILinkIn(reportUrl, "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";
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -570,21 +570,46 @@ var ClickEventHandler = {
 
   onAboutBlocked(targetElement, ownerDoc) {
     var reason = "phishing";
     if (/e=malwareBlocked/.test(ownerDoc.documentURI)) {
       reason = "malware";
     } else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
       reason = "unwanted";
     }
+
+    let docShell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+                                       .getInterface(Ci.nsIWebNavigation)
+                                      .QueryInterface(Ci.nsIDocShell);
+    let blockedInfo = {};
+    if (docShell.failedChannel) {
+      let classifiedChannel = docShell.failedChannel.
+                              QueryInterface(Ci.nsIClassifiedChannel);
+      if (classifiedChannel) {
+        let httpChannel = docShell.failedChannel.QueryInterface(Ci.nsIHttpChannel);
+
+        let reportUri = httpChannel.URI.clone();
+
+        // Remove the query to avoid leaking sensitive data
+        if (reportUri instanceof Ci.nsIURL) {
+          reportUri.query = "";
+        }
+
+        blockedInfo = { list: classifiedChannel.matchedList,
+                        provider: classifiedChannel.matchedProvider,
+                        uri: reportUri.asciiSpec };
+      }
+    }
+
     sendAsyncMessage("Browser:SiteBlockedError", {
       location: ownerDoc.location.href,
       reason,
       elementId: targetElement.getAttribute("id"),
-      isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
+      isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
+      blockedInfo
     });
   },
 
   onAboutNetError(event, documentURI) {
     let elmId = event.originalTarget.getAttribute("id");
     if (elmId == "returnButton") {
       sendAsyncMessage("Browser:SSLErrorGoBack", {});
       return;
--- a/dom/ipc/PURLClassifier.ipdl
+++ b/dom/ipc/PURLClassifier.ipdl
@@ -3,27 +3,28 @@
  */
 /* 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/. */
 
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
 include protocol PContent;
+include PURLClassifierInfo;
 
 namespace mozilla {
 namespace dom {
 
-union MaybeResult {
-  nsresult;
+union MaybeInfo {
+  ClassifierInfo;
   void_t;
 };
 
 protocol PURLClassifier
 {
   manager PContent;
 
 child:
-  async __delete__(MaybeResult status);
+  async __delete__(MaybeInfo info, nsresult errorCode);
 };
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PURLClassifierInfo.ipdlh
@@ -0,0 +1,17 @@
+/* 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/. */
+
+namespace mozilla {
+namespace dom {
+
+struct ClassifierInfo {
+  nsCString list;
+  nsCString provider;
+  nsCString prefix;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+
--- a/dom/ipc/URLClassifierChild.cpp
+++ b/dom/ipc/URLClassifierChild.cpp
@@ -1,20 +1,24 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "URLClassifierChild.h"
 #include "nsComponentManagerUtils.h"
+#include "nsIURI.h"
 
 using namespace mozilla::dom;
 
 mozilla::ipc::IPCResult
-URLClassifierChild::Recv__delete__(const MaybeResult& aResult)
+URLClassifierChild::Recv__delete__(const MaybeInfo& aInfo,
+                                   const nsresult& aResult)
 {
   MOZ_ASSERT(mCallback);
-  if (aResult.type() == MaybeResult::Tnsresult) {
-    mCallback->OnClassifyComplete(aResult.get_nsresult());
+  if (aInfo.type() == MaybeInfo::TClassifierInfo) {
+    mCallback->OnClassifyComplete(aResult, aInfo.get_ClassifierInfo().list(),
+                                  aInfo.get_ClassifierInfo().provider(),
+                                  aInfo.get_ClassifierInfo().prefix());
   }
   return IPC_OK();
 }
--- a/dom/ipc/URLClassifierChild.h
+++ b/dom/ipc/URLClassifierChild.h
@@ -17,17 +17,18 @@ class URLClassifierChild : public PURLCl
 {
  public:
   URLClassifierChild() = default;
 
   void SetCallback(nsIURIClassifierCallback* aCallback)
   {
     mCallback = aCallback;
   }
-  mozilla::ipc::IPCResult Recv__delete__(const MaybeResult& aResult) override;
+  mozilla::ipc::IPCResult Recv__delete__(const MaybeInfo& aInfo,
+                                         const nsresult& aResult) override;
 
  private:
   ~URLClassifierChild() = default;
 
   nsCOMPtr<nsIURIClassifierCallback> mCallback;
 };
 
 } // namespace dom
--- a/dom/ipc/URLClassifierParent.cpp
+++ b/dom/ipc/URLClassifierParent.cpp
@@ -37,29 +37,37 @@ URLClassifierParent::StartClassify(nsIPr
     // on its callback if some classification actually happens.
     *aSuccess = false;
     ClassificationFailed();
   }
   return IPC_OK();
 }
 
 nsresult
-URLClassifierParent::OnClassifyComplete(nsresult aRv)
+URLClassifierParent::OnClassifyComplete(nsresult aErrorCode,
+                                        const nsACString& aList,
+                                        const nsACString& aProvider,
+                                        const nsACString& aPrefix)
 {
   if (mIPCOpen) {
-    Unused << Send__delete__(this, aRv);
+    ClassifierInfo info;
+    info.list() = aList;
+    info.prefix() = aPrefix;
+    info.provider() = aProvider;
+
+    Unused << Send__delete__(this, info, aErrorCode);
   }
   return NS_OK;
 }
 
 void
 URLClassifierParent::ClassificationFailed()
 {
   if (mIPCOpen) {
-    Unused << Send__delete__(this, void_t());
+    Unused << Send__delete__(this, void_t(), NS_ERROR_FAILURE);
   }
 }
 
 void
 URLClassifierParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   mIPCOpen = false;
 }
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -97,16 +97,17 @@ IPDL_SOURCES += [
     'PDatePicker.ipdl',
     'PDocumentRenderer.ipdl',
     'PFilePicker.ipdl',
     'PPluginWidget.ipdl',
     'PProcessHangMonitor.ipdl',
     'PScreenManager.ipdl',
     'PTabContext.ipdlh',
     'PURLClassifier.ipdl',
+    'PURLClassifierInfo.ipdlh',
     'ServiceWorkerConfiguration.ipdlh',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_TARGET'] == 'Darwin':
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5184,28 +5184,31 @@ pref("browser.safebrowsing.downloads.rem
 pref("browser.safebrowsing.debug", false);
 
 // The protocol version we communicate with google server.
 pref("browser.safebrowsing.provider.google.pver", "2.2");
 pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,googpub-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
 pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
 pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+pref("browser.safebrowsing.provider.google.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.provider.google.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
+
 
 // Prefs for v4.
 pref("browser.safebrowsing.provider.google4.pver", "4");
 pref("browser.safebrowsing.provider.google4.lists", "goog-phish-proto,googpub-phish-proto,goog-malware-proto,goog-unwanted-proto");
 pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%");
 // Leave it empty until we roll out v4 hash completion feature. See Bug 1323856.
 pref("browser.safebrowsing.provider.google4.gethashURL", "");
 pref("browser.safebrowsing.provider.google4.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
-
-pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.provider.google4.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.provider.google4.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
+
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
-pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 
 // The table and global pref for blocking plugin content
 pref("browser.safebrowsing.blockedURIs.enabled", true);
 pref("urlclassifier.blockedTable", "test-block-simple,mozplugin-block-digest256");
 
 // The protocol version we communicate with mozilla server.
 pref("browser.safebrowsing.provider.mozilla.pver", "2.2");
 pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256");
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -27,16 +27,17 @@ XPIDL_SOURCES += [
     'nsICacheInfoChannel.idl',
     'nsICachingChannel.idl',
     'nsICancelable.idl',
     'nsICaptivePortalService.idl',
     'nsIChannel.idl',
     'nsIChannelEventSink.idl',
     'nsIChannelWithDivertableParentListener.idl',
     'nsIChildChannel.idl',
+    'nsIClassifiedChannel.idl',
     'nsIClassOfService.idl',
     'nsIContentSniffer.idl',
     'nsICryptoFIPSInfo.idl',
     'nsICryptoHash.idl',
     'nsICryptoHMAC.idl',
     'nsIDashboard.idl',
     'nsIDashboardEventNotifier.idl',
     'nsIDeprecationWarner.idl',
--- a/netwerk/base/nsChannelClassifier.cpp
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -306,17 +306,18 @@ nsChannelClassifier::NotifyTrackingProte
 
 void
 nsChannelClassifier::Start()
 {
   nsresult rv = StartInternal();
   if (NS_FAILED(rv)) {
     // If we aren't getting a callback for any reason, assume a good verdict and
     // make sure we resume the channel if necessary.
-    OnClassifyComplete(NS_OK);
+    OnClassifyComplete(NS_OK, NS_LITERAL_CSTRING(""),NS_LITERAL_CSTRING(""),
+                       NS_LITERAL_CSTRING(""));
   }
 }
 
 nsresult
 nsChannelClassifier::StartInternal()
 {
     // Should only be called in the parent process.
     MOZ_ASSERT(XRE_IsParentProcess());
@@ -572,29 +573,43 @@ nsChannelClassifier::SameLoadingURI(nsID
   }
   bool equals = false;
   nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals);
   return NS_SUCCEEDED(rv) && equals;
 }
 
 // static
 nsresult
-nsChannelClassifier::SetBlockedTrackingContent(nsIChannel *channel)
+nsChannelClassifier::SetBlockedContent(nsIChannel *channel,
+                                       nsresult aErrorCode,
+                                       const nsACString& aList,
+                                       const nsACString& aProvider,
+                                       const nsACString& aPrefix)
 {
+  NS_ENSURE_ARG(!aList.IsEmpty());
+  NS_ENSURE_ARG(!aPrefix.IsEmpty());
+
   // Can be called in EITHER the parent or child process.
   nsCOMPtr<nsIParentChannel> parentChannel;
   NS_QueryNotificationCallbacks(channel, parentChannel);
   if (parentChannel) {
-    // This channel is a parent-process proxy for a child process request. The
-    // actual channel will be notified via the status passed to
-    // nsIRequest::Cancel and do this for us.
+    // This channel is a parent-process proxy for a child process request.
+    // Tell the child process channel to do this instead.
+    parentChannel->SetClassifierMatchedInfo(aList, aProvider, aPrefix);
     return NS_OK;
   }
 
   nsresult rv;
+  nsCOMPtr<nsIClassifiedChannel> classifiedChannel = do_QueryInterface(channel, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (classifiedChannel) {
+    classifiedChannel->SetMatchedInfo(aList, aProvider, aPrefix);
+  }
+
   nsCOMPtr<mozIDOMWindowProxy> win;
   nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
     do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, NS_OK);
   rv = thirdPartyUtil->GetTopWindowForChannel(channel, getter_AddRefs(win));
   NS_ENSURE_SUCCESS(rv, NS_OK);
   auto* pwin = nsPIDOMWindowOuter::From(win);
   nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
@@ -617,31 +632,42 @@ nsChannelClassifier::SetBlockedTrackingC
   nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
   NS_ENSURE_SUCCESS(rv, NS_OK);
   uint32_t state = 0;
   nsCOMPtr<nsISecureBrowserUI> securityUI;
   docShell->GetSecurityUI(getter_AddRefs(securityUI));
   if (!securityUI) {
     return NS_OK;
   }
-  doc->SetHasTrackingContentBlocked(true);
   securityUI->GetState(&state);
-  state |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
+  if (aErrorCode == NS_ERROR_TRACKING_URI) {
+    doc->SetHasTrackingContentBlocked(true);
+    state |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
+  } else {
+    state |= nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
+  }
+
   eventSink->OnSecurityChange(nullptr, state);
 
   // Log a warning to the web console.
   nsCOMPtr<nsIURI> uri;
   channel->GetURI(getter_AddRefs(uri));
   NS_ConvertUTF8toUTF16 spec(uri->GetSpecOrDefault());
   const char16_t* params[] = { spec.get() };
+  const char* message = (aErrorCode == NS_ERROR_TRACKING_URI) ?
+    "TrackingUriBlocked" : "UnsafeUriBlocked";
+  nsCString category = (aErrorCode == NS_ERROR_TRACKING_URI) ?
+    NS_LITERAL_CSTRING("Tracking Protection") :
+    NS_LITERAL_CSTRING("Safe Browsing");
+
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                  NS_LITERAL_CSTRING("Tracking Protection"),
+                                  category,
                                   doc,
                                   nsContentUtils::eNECKO_PROPERTIES,
-                                  "TrackingUriBlocked",
+                                  message,
                                   params, ArrayLength(params));
 
   return NS_OK;
 }
 
 nsresult
 nsChannelClassifier::IsTrackerWhitelisted()
 {
@@ -702,17 +728,20 @@ nsChannelClassifier::IsTrackerWhiteliste
   }
 
   LOG(("nsChannelClassifier[%p]: %s is not in the whitelist",
        this, whitelistEntry.get()));
   return NS_ERROR_TRACKING_URI;
 }
 
 NS_IMETHODIMP
-nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode)
+nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode,
+                                        const nsACString& aList,
+                                        const nsACString& aProvider,
+                                        const nsACString& aPrefix)
 {
     // Should only be called in the parent process.
     MOZ_ASSERT(XRE_IsParentProcess());
 
     if (aErrorCode == NS_ERROR_TRACKING_URI &&
         NS_SUCCEEDED(IsTrackerWhitelisted())) {
       LOG(("nsChannelClassifier[%p]:OnClassifyComplete tracker found "
            "in whitelist so we won't block it", this));
@@ -769,22 +798,21 @@ nsChannelClassifier::OnClassifyComplete(
         if (LOG_ENABLED()) {
           nsCOMPtr<nsIURI> uri;
           mChannel->GetURI(getter_AddRefs(uri));
           LOG(("nsChannelClassifier[%p]: cancelling channel %p for %s "
                "with error code %s", this, mChannel.get(),
                uri->GetSpecOrDefault().get(), errorName.get()));
         }
 
-        // Channel will be cancelled (page element blocked) due to tracking.
+        // Channel will be cancelled (page element blocked) due to tracking
+        // protection or Safe Browsing.
         // Do update the security state of the document and fire a security
         // change event.
-        if (aErrorCode == NS_ERROR_TRACKING_URI) {
-          SetBlockedTrackingContent(mChannel);
-        }
+        SetBlockedContent(mChannel, aErrorCode, aList, aProvider, aPrefix);
 
         mChannel->Cancel(aErrorCode);
       }
       LOG(("nsChannelClassifier[%p]: resuming channel %p from "
            "OnClassifyComplete", this, mChannel.get()));
       mChannel->Resume();
     }
 
--- a/netwerk/base/nsChannelClassifier.h
+++ b/netwerk/base/nsChannelClassifier.h
@@ -56,18 +56,22 @@ private:
     // Checks that the channel was loaded by the URI currently loaded in aDoc
     static bool SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel);
 
     nsresult ShouldEnableTrackingProtectionInternal(nsIChannel *aChannel,
                                                     bool *result);
 
     bool AddonMayLoad(nsIChannel *aChannel, nsIURI *aUri);
 public:
-    // If we are blocking tracking content, update the corresponding flag in
-    // the respective docshell and call nsISecurityEventSink::onSecurityChange.
-    static nsresult SetBlockedTrackingContent(nsIChannel *channel);
+    // If we are blocking content, update the corresponding flag in the respective
+    // docshell and call nsISecurityEventSink::onSecurityChange.
+    static nsresult SetBlockedContent(nsIChannel *channel,
+                                      nsresult aErrorCode,
+                                      const nsACString& aList,
+                                      const nsACString& aProvider,
+                                      const nsACString& aPrefix);
     static nsresult NotifyTrackingProtectionDisabled(nsIChannel *aChannel);
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIClassifiedChannel.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; 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"
+
+/**
+ * nsIClassifiedChannel
+ *
+ * A channel may optionally implement this interface if it carries classified
+ * result information of channel classifier. The information contains, for
+ * example, the name of matched table and the name of matched provider.
+ */
+[scriptable, uuid(70cf6091-a1de-4aa8-8224-058f8964be31)]
+interface nsIClassifiedChannel : nsISupports
+{
+  /**
+   * Sets matched info of the classified channel.
+   *
+   * @param aList
+   *        Name of the Safe Browsing list that matched (e.g. goog-phish-shavar).
+   * @param aProvider
+   *        Name of the Safe Browsing provider that matched (e.g. google)
+   * @param aPrefix
+   *        Hash prefix of URL that matched Safe Browsing list.
+   */
+  void setMatchedInfo(in ACString aList,
+                      in ACString aProvider,
+                      in ACString aPrefix);
+
+  /**
+   * Name of the list that matched
+   */
+  readonly attribute ACString matchedList;
+
+  /**
+   * Name of provider that matched
+   */
+  readonly attribute ACString matchedProvider;
+
+  /**
+   * Hash prefix of URL that matched
+   */
+  readonly attribute ACString matchedPrefix;
+
+};
--- a/netwerk/base/nsIParentChannel.idl
+++ b/netwerk/base/nsIParentChannel.idl
@@ -29,16 +29,29 @@ interface nsIParentChannel : nsIStreamLi
   [noscript] void setParentListener(in HttpChannelParentListener listener);
 
   /**
    * Called to notify the HttpChannelChild that tracking protection was
    * disabled for this load.
    */
   [noscript] void notifyTrackingProtectionDisabled();
 
+   /**
+   * Called to set matched information when URL matches SafeBrowsing list.
+   * @param aList
+   *        Name of the list that matched
+   * @param aProvider
+   *        Name of provider that matched
+   * @param aPrefix
+   *        String represents hash prefix that matched
+   */
+  [noscript] void setClassifierMatchedInfo(in ACString aList,
+                                           in ACString aProvider,
+                                           in ACString aPrefix);
+
   /**
    * Called to notify the HttpChannelChild that the resource being loaded
    * is on the tracking protection list.
    */
   [noscript] void notifyTrackingResource();
 
   /**
    * Called to invoke deletion of the IPC protocol.
--- a/netwerk/base/nsIURIClassifier.idl
+++ b/netwerk/base/nsIURIClassifier.idl
@@ -24,18 +24,27 @@ interface nsIURIClassifierCallback : nsI
    * Called by the URI classifier service when it is done checking a URI.
    *
    * Clients are responsible for associating callback objects with classify()
    * calls.
    *
    * @param aErrorCode
    *        The error code with which the channel should be cancelled, or
    *        NS_OK if the load should continue normally.
+   * @param aList
+   *        Name of the list that matched
+   * @param aProvider
+   *        Name of provider that matched
+   * @param aPrefix
+   *        Hash prefix of URL that matched
    */
-  void onClassifyComplete(in nsresult aErrorCode);
+  void onClassifyComplete(in nsresult aErrorCode,
+                          in ACString aList,
+                          in ACString aProvider,
+                          in ACString aPrefix);
 };
 
 /**
  * The URI classifier service checks a URI against lists of phishing
  * and malware sites.
  */
 [scriptable, uuid(596620cc-76e3-4133-9d90-360e59a794cf)]
 interface nsIURIClassifier : nsISupports
--- a/netwerk/locales/en-US/necko.properties
+++ b/netwerk/locales/en-US/necko.properties
@@ -34,16 +34,17 @@ DirColMTime=Last Modified
 DirFileLabel=File: 
 
 PhishingAuth=You are about to visit “%1$S”. This site may be attempting to trick you into thinking you are visiting a different site. Use extreme caution.
 PhishingAuthAccept=I understand and will be very careful
 SuperfluousAuth=You are about to log in to the site “%1$S” with the username “%2$S”, but the website does not require authentication. This may be an attempt to trick you.\n\nIs “%1$S” the site you want to visit?
 AutomaticAuth=You are about to log in to the site “%1$S” with the username “%2$S”.
 
 TrackingUriBlocked=The resource at “%1$S” was blocked because tracking protection is enabled.
+UnsafeUriBlocked=The resource at “%1$S” was blocked by Safe Browsing.
 
 # LOCALIZATION NOTE (APIDeprecationWarning):
 # %1$S is the deprecated API; %2$S is the API function that should be used.
 APIDeprecationWarning=Warning: ‘%1$S’ deprecated, please use ‘%2$S’
 
 # LOCALIZATION NOTE (nsICookieManagerDeprecated): don't localize originAttributes.
 # %1$S is the deprecated API; %2$S is the interface suffix that the given deprecated API belongs to.
 nsICookieManagerAPIDeprecated=“%1$S” is changed. Update your code and pass the correct originAttributes. Read more on MDN: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager%2$S
--- a/netwerk/protocol/data/DataChannelParent.cpp
+++ b/netwerk/protocol/data/DataChannelParent.cpp
@@ -45,16 +45,25 @@ DataChannelParent::NotifyTrackingProtect
 NS_IMETHODIMP
 DataChannelParent::NotifyTrackingResource()
 {
     // Nothing to do.
     return NS_OK;
 }
 
 NS_IMETHODIMP
+DataChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+                                            const nsACString& aProvider,
+                                            const nsACString& aPrefix)
+{
+  // nothing to do
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 DataChannelParent::Delete()
 {
     // Nothing to do.
     return NS_OK;
 }
 
 void
 DataChannelParent::ActorDestroy(ActorDestroyReason why)
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -566,16 +566,25 @@ FTPChannelParent::NotifyTrackingProtecti
 NS_IMETHODIMP
 FTPChannelParent::NotifyTrackingResource()
 {
   // One day, this should probably be filled in.
   return NS_OK;
 }
 
 NS_IMETHODIMP
+FTPChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+                                           const nsACString& aProvider,
+                                           const nsACString& aPrefix)
+{
+  // One day, this should probably be filled in.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 FTPChannelParent::Delete()
 {
   if (mIPCClosed || !SendDeleteSelf())
     return NS_ERROR_UNEXPECTED;
 
   return NS_OK;
 }
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -340,16 +340,17 @@ NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
   NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
   NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
   NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
   NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
   NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
   NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
   NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
   NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
+  NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
   if (aIID.Equals(NS_GET_IID(HttpBaseChannel))) {
     foundInterface = static_cast<nsIWritablePropertyBag*>(this);
   } else
 NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
 
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsIRequest
 //-----------------------------------------------------------------------------
@@ -3377,16 +3378,51 @@ bool
 HttpBaseChannel::SameOriginWithOriginalUri(nsIURI *aURI)
 {
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsresult rv = ssm->CheckSameOriginURI(aURI, mOriginalURI, false);
   return (NS_SUCCEEDED(rv));
 }
 
 
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIClassifiedChannel
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedList(nsACString& aList)
+{
+  aList = mMatchedList;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedProvider(nsACString& aProvider)
+{
+  aProvider = mMatchedProvider;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedPrefix(nsACString& aPrefix)
+{
+  aPrefix = mMatchedPrefix;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedInfo(const nsACString& aList,
+                                const nsACString& aProvider,
+                                const nsACString& aPrefix) {
+  NS_ENSURE_ARG(!aList.IsEmpty());
+
+  mMatchedList = aList;
+  mMatchedProvider = aProvider;
+  mMatchedPrefix = aPrefix;
+  return NS_OK;
+}
 
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsITimedChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::SetTimingEnabled(bool enabled) {
   mTimingEnabled = enabled;
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -24,16 +24,17 @@
 #include "nsIFormPOSTActionChannel.h"
 #include "nsIUploadChannel2.h"
 #include "nsIProgressEventSink.h"
 #include "nsIURI.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIStringEnumerator.h"
 #include "nsISupportsPriority.h"
 #include "nsIClassOfService.h"
+#include "nsIClassifiedChannel.h"
 #include "nsIApplicationCache.h"
 #include "nsIResumableChannel.h"
 #include "nsITraceableChannel.h"
 #include "nsILoadContext.h"
 #include "nsILoadInfo.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "nsThreadUtils.h"
 #include "PrivateBrowsingChannel.h"
@@ -85,28 +86,30 @@ class HttpBaseChannel : public nsHashPro
                       , public nsIClassOfService
                       , public nsIResumableChannel
                       , public nsITraceableChannel
                       , public PrivateBrowsingChannel<HttpBaseChannel>
                       , public nsITimedChannel
                       , public nsIForcePendingChannel
                       , public nsIConsoleReportCollector
                       , public nsIThrottledInputChannel
+                      , public nsIClassifiedChannel
 {
 protected:
   virtual ~HttpBaseChannel();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIUPLOADCHANNEL
   NS_DECL_NSIFORMPOSTACTIONCHANNEL
   NS_DECL_NSIUPLOADCHANNEL2
   NS_DECL_NSITRACEABLECHANNEL
   NS_DECL_NSITIMEDCHANNEL
   NS_DECL_NSITHROTTLEDINPUTCHANNEL
+  NS_DECL_NSICLASSIFIEDCHANNEL
 
   NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_BASE_CHANNEL_IID)
 
   HttpBaseChannel();
 
   virtual nsresult Init(nsIURI *aURI, uint32_t aCaps, nsProxyInfo *aProxyInfo,
                         uint32_t aProxyResolveFlags,
                         nsIURI *aProxyURI,
@@ -595,16 +598,21 @@ protected:
   nsCString mAvailableCachedAltDataType;
 
   bool mForceMainDocumentChannel;
   bool mIsTrackingResource;
 
   nsID mChannelId;
 
   nsString mIntegrityMetadata;
+
+  // Classified channel's matched information
+  nsCString mMatchedList;
+  nsCString mMatchedProvider;
+  nsCString mMatchedPrefix;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(HttpBaseChannel, HTTP_BASE_CHANNEL_IID)
 
 // Share some code while working around C++'s absurd inability to handle casting
 // of member functions between base/derived types.
 // - We want to store member function pointer to call at resume time, but one
 //   such function--HandleAsyncAbort--we want to share between the
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -964,18 +964,33 @@ void
 HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus, nsISupports* aContext)
 {
   LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this));
   MOZ_ASSERT(!mIsPending);
 
   // NB: We use aChannelStatus here instead of mStatus because if there was an
   // nsCORSListenerProxy on this request, it will override the tracking
   // protection's return value.
-  if (aChannelStatus == NS_ERROR_TRACKING_URI) {
-    nsChannelClassifier::SetBlockedTrackingContent(this);
+  if (aChannelStatus == NS_ERROR_TRACKING_URI ||
+      aChannelStatus == NS_ERROR_MALWARE_URI ||
+      aChannelStatus == NS_ERROR_UNWANTED_URI ||
+      aChannelStatus == NS_ERROR_BLOCKED_URI ||
+      aChannelStatus == NS_ERROR_PHISHING_URI) {
+    nsCString list, provider, prefix;
+
+    nsresult rv = GetMatchedList(list);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    rv = GetMatchedProvider(provider);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    rv = GetMatchedPrefix(prefix);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    nsChannelClassifier::SetBlockedContent(this, aChannelStatus, list, provider, prefix);
   }
 
   MOZ_ASSERT(!mOnStopRequestCalled,
              "We should not call OnStopRequest twice");
 
   // In theory mListener should not be null, but in practice sometimes it is.
   MOZ_ASSERT(mListener);
   if (mListener) {
@@ -1513,16 +1528,24 @@ HttpChannelChild::FlushedForDiversion()
   // down. After it is set, no OnStart/OnData/OnStop callbacks should be
   // received from the parent channel, nor dequeued from the ChannelEventQueue.
   mFlushedForDiversion = true;
 
   SendDivertComplete();
 }
 
 mozilla::ipc::IPCResult
+HttpChannelChild::RecvSetClassifierMatchedInfo(const ClassifierInfo& aInfo)
+{
+  SetMatchedInfo(aInfo.list(), aInfo.provider(), aInfo.prefix());
+  return IPC_OK();
+}
+
+
+mozilla::ipc::IPCResult
 HttpChannelChild::RecvDivertMessages()
 {
   LOG(("HttpChannelChild::RecvDivertMessages [this=%p]\n", this));
   MOZ_RELEASE_ASSERT(mDivertingToParent);
   MOZ_RELEASE_ASSERT(mSuspendCount > 0);
 
   // DivertTo() has been called on parent, so we can now start sending queued
   // IPDL messages back to parent listener.
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -103,16 +103,17 @@ public:
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
   bool IsSuspended();
 
   mozilla::ipc::IPCResult RecvNotifyTrackingProtectionDisabled() override;
   mozilla::ipc::IPCResult RecvNotifyTrackingResource() override;
   void FlushedForDiversion();
+  mozilla::ipc::IPCResult RecvSetClassifierMatchedInfo(const ClassifierInfo& aInfo) override;
 
 protected:
   mozilla::ipc::IPCResult RecvOnStartRequest(const nsresult& channelStatus,
                                              const nsHttpResponseHead& responseHead,
                                              const bool& useResponseHead,
                                              const nsHttpHeaderArray& requestHeaders,
                                              const bool& isFromCache,
                                              const bool& cacheEntryAvailable,
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -36,16 +36,17 @@
 #include "mozilla/BasePrincipal.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIIPCSerializableInputStream.h"
 #include "nsIPrompt.h"
 #include "nsIWindowWatcher.h"
 #include "nsIDocument.h"
 #include "nsStringStream.h"
 #include "nsQueryObject.h"
+#include "nsIURIClassifier.h"
 
 using mozilla::BasePrincipal;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
@@ -1359,16 +1360,32 @@ NS_IMETHODIMP
 HttpChannelParent::NotifyTrackingProtectionDisabled()
 {
   if (!mIPCClosed)
     Unused << SendNotifyTrackingProtectionDisabled();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+HttpChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+                                            const nsACString& aProvider,
+                                            const nsACString& aPrefix)
+{
+  if (!mIPCClosed) {
+    ClassifierInfo info;
+    info.list() = aList;
+    info.prefix() = aPrefix;
+    info.provider() = aProvider;
+
+    Unused << SendSetClassifierMatchedInfo(info);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 HttpChannelParent::NotifyTrackingResource()
 {
   if (!mIPCClosed)
     Unused << SendNotifyTrackingResource();
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -5,16 +5,17 @@
  * 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 protocol PNecko;
 include InputStreamParams;
 include URIParams;
 include PBackgroundSharedTypes;
 include NeckoChannelParams;
+include PURLClassifierInfo;
 
 include protocol PBlob; //FIXME: bug #792908
 
 include "mozilla/net/NeckoMessageUtils.h";
 
 using class nsHttpHeaderArray from "nsHttpHeaderArray.h";
 using mozilla::net::NetAddr from "mozilla/net/DNS.h";
 using struct mozilla::net::ResourceTimingStruct from "mozilla/net/TimingStruct.h";
@@ -163,16 +164,19 @@ child:
 
   // Tell child to delete channel (all IPDL deletes must be done from child to
   // avoid races: see bug 591708).
   async DeleteSelf();
 
   // Tell the child to issue a deprecation warning.
   async IssueDeprecationWarning(uint32_t warning, bool asError);
 
+  // Tell the child information of matched URL againts SafeBrowsing list
+  async SetClassifierMatchedInfo(ClassifierInfo info);
+
 both:
   // After receiving this message, the parent also calls
   // SendFinishInterceptedRedirect, and makes sure not to send any more messages
   // after that. When receiving this message, the child will call
   // Send__delete__() and complete the steps required to finish the redirect.
   async FinishInterceptedRedirect();
 
   async SetPriority(int16_t priority);
--- a/toolkit/components/url-classifier/Entries.h
+++ b/toolkit/components/url-classifier/Entries.h
@@ -12,20 +12,17 @@
 
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsICryptoHash.h"
 #include "nsNetUtil.h"
 #include "nsIOutputStream.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
-
-#if DEBUG
 #include "plbase64.h"
-#endif
 
 namespace mozilla {
 namespace safebrowsing {
 
 #define PREFIX_SIZE   4
 #define COMPLETE_SIZE 32
 
 // This is the struct that contains 4-byte hash prefixes.
@@ -80,24 +77,26 @@ struct SafebrowsingHash
   bool operator!=(const self_type& aOther) const {
     return Comparator::Compare(buf, aOther.buf) != 0;
   }
 
   bool operator<(const self_type& aOther) const {
     return Comparator::Compare(buf, aOther.buf) < 0;
   }
 
-#ifdef DEBUG
   void ToString(nsACString& aStr) const {
     uint32_t len = ((sHashSize + 2) / 3) * 4;
+
+    // Capacity should be one greater than length, because PL_Base64Encode
+    // will not be null-terminated, while nsCString requires it.
     aStr.SetCapacity(len + 1);
     PL_Base64Encode((char*)buf, sHashSize, aStr.BeginWriting());
     aStr.BeginWriting()[len] = '\0';
+    aStr.SetLength(len);
   }
-#endif
 
   void ToHexString(nsACString& aStr) const {
     static const char* const lut = "0123456789ABCDEF";
     // 32 bytes is the longest hash
     size_t len = 32;
 
     aStr.SetCapacity(2 * len);
     for (size_t i = 0; i < len; ++i) {
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -141,44 +141,47 @@ this.SafeBrowsing = {
   trackingProtectionWhitelists: [],
   blockedLists:                 [],
 
   updateURL:             null,
   gethashURL:            null,
 
   reportURL:             null,
 
-  getReportURL: function(kind, URI) {
+  getReportURL: function(kind, info) {
     let pref;
     switch (kind) {
       case "Phish":
         pref = "browser.safebrowsing.reportPhishURL";
         break;
+
       case "PhishMistake":
-        pref = "browser.safebrowsing.reportPhishMistakeURL";
-        break;
       case "MalwareMistake":
-        pref = "browser.safebrowsing.reportMalwareMistakeURL";
+        pref = "browser.safebrowsing.provider." + info.provider + ".report" + kind + "URL";
         break;
 
       default:
         let err = "SafeBrowsing getReportURL() called with unknown kind: " + kind;
         Components.utils.reportError(err);
         throw err;
     }
-    let reportUrl = Services.urlFormatter.formatURLPref(pref);
 
-    let pageUri = URI.clone();
+    if (!info.list || !info.uri) {
+      return null;
+    }
 
-    // Remove the query to avoid including potentially sensitive data
-    if (pageUri instanceof Ci.nsIURL)
-      pageUri.query = '';
+    let reportUrl = Services.urlFormatter.formatURLPref(pref);
+    // formatURLPref might return "about:blank" if getting the pref fails
+    if (reportUrl == "about:blank") {
+      reportUrl = null;
+    }
 
-    reportUrl += encodeURIComponent(pageUri.asciiSpec);
-
+    if (reportUrl) {
+      reportUrl += encodeURIComponent(info.uri);
+    }
     return reportUrl;
   },
 
   observe: function(aSubject, aTopic, aData) {
     // skip nextupdatetime and lastupdatetime
     if (aData.indexOf("lastupdatetime") >= 0 || aData.indexOf("nextupdatetime") >= 0) {
       return;
     }
--- a/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
+++ b/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
@@ -225,8 +225,25 @@ interface nsIUrlClassifierLookupCallback
    * @param results
    *        If this parameter is null, there were no results found.
    *        If not, it contains an array of nsUrlClassifierEntry objects
    *        with possible matches.  The callee is responsible for freeing
    *        this array.
    */
   void lookupComplete(in ResultArray results);
 };
+
+/**
+ * This is an internal helper interface which is called after each
+ * classify completes to provide and handle a set of possible results,
+ * which the main thread may need to expand using an nsIURIClassifierCallback.
+ */
+[uuid(091adf98-28a5-473d-8dec-5b34b4e62496)]
+interface nsIUrlClassifierClassifyCallback : nsISupports
+{
+  /**
+   * The function is called each time the URL matches a Safe Browsing list
+   * The function could be called multiple times if URL matches multiple lists
+   *
+   */
+  void handleResult(in ACString aList,
+                    in ACString aPrefix);
+};
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -1109,16 +1109,19 @@ nsUrlClassifierLookupCallback::HandleRes
     return mCallback->HandleEvent(NS_LITERAL_CSTRING(""));
   }
   MOZ_ASSERT(mPendingCompletions == 0, "HandleResults() should never be "
              "called while there are pending completions");
 
   LOG(("nsUrlClassifierLookupCallback::HandleResults [%p, %" PRIuSIZE " results]",
        this, mResults->Length()));
 
+  nsCOMPtr<nsIUrlClassifierClassifyCallback> classifyCallback =
+    do_QueryInterface(mCallback);
+
   nsTArray<nsCString> tables;
   // Build a stringified list of result tables.
   for (uint32_t i = 0; i < mResults->Length(); i++) {
     LookupResult& result = mResults->ElementAt(i);
 
     // Leave out results that weren't confirmed, as their existence on
     // the list can't be verified.  Also leave out randomly-generated
     // noise.
@@ -1133,16 +1136,22 @@ nsUrlClassifierLookupCallback::HandleRes
     }
 
     LOG(("Confirmed result %s from table %s",
          result.PartialHashHex().get(), result.mTableName.get()));
 
     if (tables.IndexOf(result.mTableName) == nsTArray<nsCString>::NoIndex) {
       tables.AppendElement(result.mTableName);
     }
+
+    if (classifyCallback) {
+      nsCString prefixString;
+      result.hash.fixedLengthPrefix.ToString(prefixString);
+      classifyCallback->HandleResult(result.mTableName, prefixString);
+    }
   }
 
   // TODO: Bug 1333328, Refactor cache miss mechanism for v2.
   // Some parts of this gethash request generated no hits at all.
   // Prefixes must have been removed from the database since our last update.
   // Save the prefixes we checked to prevent repeated requests
   // until the next update.
   nsAutoPtr<PrefixArray> cacheMisses(new PrefixArray());
@@ -1168,48 +1177,123 @@ nsUrlClassifierLookupCallback::HandleRes
     if (i != 0)
       tableStr.Append(',');
     tableStr.Append(tables[i]);
   }
 
   return mCallback->HandleEvent(tableStr);
 }
 
+struct Provider {
+  nsCString name;
+  uint8_t priority;
+};
+
+// Order matters
+// Provider which is not included in this table has the lowest priority 0
+static const Provider kBuiltInProviders[] = {
+  { NS_LITERAL_CSTRING("mozilla"), 1 },
+  { NS_LITERAL_CSTRING("google4"), 2 },
+  { NS_LITERAL_CSTRING("google"), 3 },
+};
 
 // -------------------------------------------------------------------------
-// Helper class for nsIURIClassifier implementation, translates table names
-// to nsIURIClassifier enums.
+// Helper class for nsIURIClassifier implementation, handle classify result and
+// send back to nsIURIClassifier
 
-class nsUrlClassifierClassifyCallback final : public nsIUrlClassifierCallback
+class nsUrlClassifierClassifyCallback final : public nsIUrlClassifierCallback,
+                                              public nsIUrlClassifierClassifyCallback
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIURLCLASSIFIERCALLBACK
+  NS_DECL_NSIURLCLASSIFIERCLASSIFYCALLBACK
 
   explicit nsUrlClassifierClassifyCallback(nsIURIClassifierCallback *c)
     : mCallback(c)
     {}
 
 private:
-  ~nsUrlClassifierClassifyCallback() {}
+
+  struct ClassifyMatchedInfo {
+    nsCString table;
+    nsCString prefix;
+    Provider provider;
+    nsresult errorCode;
+  };
+
+  ~nsUrlClassifierClassifyCallback() {};
 
   nsCOMPtr<nsIURIClassifierCallback> mCallback;
+  nsTArray<ClassifyMatchedInfo> mMatchedArray;
 };
 
 NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback,
-                  nsIUrlClassifierCallback)
+                  nsIUrlClassifierCallback,
+                  nsIUrlClassifierClassifyCallback)
 
 NS_IMETHODIMP
 nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables)
 {
   nsresult response = TablesToResponse(tables);
-  mCallback->OnClassifyComplete(response);
+  ClassifyMatchedInfo* matchedInfo = nullptr;
+
+  if (NS_FAILED(response)) {
+    // Filter all matched info which has correct response
+    // In the case multiple tables found, use the higher priority provider
+    nsTArray<ClassifyMatchedInfo> matches;
+    for (uint32_t i = 0; i < mMatchedArray.Length(); i++) {
+      if (mMatchedArray[i].errorCode == response &&
+          (!matchedInfo ||
+           matchedInfo->provider.priority < mMatchedArray[i].provider.priority)) {
+        matchedInfo = &mMatchedArray[i];
+      }
+    }
+  }
+
+  nsCString provider = matchedInfo ? matchedInfo->provider.name : EmptyCString();
+  nsCString prefix = matchedInfo ? matchedInfo->prefix : EmptyCString();
+  nsCString table = matchedInfo ? matchedInfo->table : EmptyCString();
+
+  mCallback->OnClassifyComplete(response, table, provider, prefix);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsUrlClassifierClassifyCallback::HandleResult(const nsACString& aTable,
+                                              const nsACString& aPrefix)
+{
+  LOG(("nsUrlClassifierClassifyCallback::HandleResult [%p, table %s prefix %s]",
+        this, PromiseFlatCString(aTable).get(), PromiseFlatCString(aPrefix).get()));
+
+  if (NS_WARN_IF(aTable.IsEmpty()) || NS_WARN_IF(aPrefix.IsEmpty())) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  ClassifyMatchedInfo* matchedInfo = mMatchedArray.AppendElement();
+  matchedInfo->table = aTable;
+  matchedInfo->prefix = aPrefix;
+
+  nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
+    do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
+
+  nsCString provider;
+  nsresult rv = urlUtil->GetProvider(aTable, provider);
+
+  matchedInfo->provider.name = NS_SUCCEEDED(rv) ? provider : EmptyCString();
+  matchedInfo->provider.priority = 0;
+  for (uint8_t i = 0; i < ArrayLength(kBuiltInProviders); i++) {
+    if (kBuiltInProviders[i].name.Equals(matchedInfo->provider.name)) {
+      matchedInfo->provider.priority = kBuiltInProviders[i].priority;
+    }
+  }
+  matchedInfo->errorCode = TablesToResponse(aTable);
+
+  return NS_OK;
+}
 
 // -------------------------------------------------------------------------
 // Proxy class implementation
 
 NS_IMPL_ADDREF(nsUrlClassifierDBService)
 NS_IMPL_RELEASE(nsUrlClassifierDBService)
 NS_INTERFACE_MAP_BEGIN(nsUrlClassifierDBService)
   // Only nsIURIClassifier is supported in the content process!
@@ -1498,16 +1582,17 @@ nsUrlClassifierDBService::Classify(nsIPr
   if (!(mCheckMalware || mCheckPhishing || aTrackingProtectionEnabled ||
         mCheckBlockedURIs)) {
     *result = false;
     return NS_OK;
   }
 
   RefPtr<nsUrlClassifierClassifyCallback> callback =
     new nsUrlClassifierClassifyCallback(c);
+
   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;
--- a/uriloader/base/nsIWebProgressListener.idl
+++ b/uriloader/base/nsIWebProgressListener.idl
@@ -195,29 +195,33 @@ interface nsIWebProgressListener : nsISu
    *
    * STATE_LOADED_MIXED_DISPLAY_CONTENT
    *   Mixed display content has been loaded. State should be STATE_IS_BROKEN.
    */
   const unsigned long STATE_BLOCKED_MIXED_DISPLAY_CONTENT = 0x00000100;
   const unsigned long STATE_LOADED_MIXED_DISPLAY_CONTENT  = 0x00000200;
 
    /**
-   * Tracking content flags
+   *  Safe Browsing blocking content flags
    *
    * May be set in addition to the State security Flags, to indicate that
-   * tracking content has been encountered.
+   * tracking or unsafe content has been encountered.
    *
    * STATE_BLOCKED_TRACKING_CONTENT
    *   Tracking content has been blocked from loading.
    *
    * STATE_LOADED_TRACKING_CONTENT
    *   Tracking content has been loaded.
+   *
+   * STATE_BLOCKED_UNSAFE_CONTENT
+   *   Content which againts SafeBrowsing list has been blocked from loading.
    */
-  const unsigned long STATE_BLOCKED_TRACKING_CONTENT = 0x00001000;
-  const unsigned long STATE_LOADED_TRACKING_CONTENT  = 0x00002000;
+  const unsigned long STATE_BLOCKED_TRACKING_CONTENT         = 0x00001000;
+  const unsigned long STATE_LOADED_TRACKING_CONTENT          = 0x00002000;
+  const unsigned long STATE_BLOCKED_UNSAFE_CONTENT           = 0x00004000;
 
   /**
    * Security Strength Flags
    *
    * These flags describe the security strength and accompany STATE_IS_SECURE
    * in a call to the onSecurityChange method.  These flags are mutually
    * exclusive.
    *
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -399,16 +399,24 @@ NS_IMETHODIMP nsExtProtocolChannel::SetP
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::NotifyTrackingProtectionDisabled()
 {
   // nothing to do
   return NS_OK;
 }
 
+NS_IMETHODIMP nsExtProtocolChannel::SetClassifierMatchedInfo(const nsACString& aList,
+                                                             const nsACString& aProvider,
+                                                             const nsACString& aPrefix)
+{
+  // nothing to do
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsExtProtocolChannel::NotifyTrackingResource()
 {
   // nothing to do
   return NS_OK;
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::Delete()
 {