Bug 1660241 - Add GeckoRuntimeSetting for SafeBrowsing provider. r=droeh,dimi,snorp
authorAgi Sferro <agi@sferro.dev>
Wed, 30 Sep 2020 19:54:47 +0000
changeset 550972 be52a545a9864f2501d2eea9c72a46543bace7a1
parent 550971 377777e1795731eb4c33e47498550334771653a0
child 550973 5a501f5b443d74a72928f2fdfad1998c60dba79f
push id127589
push userasferro@mozilla.com
push dateWed, 30 Sep 2020 21:37:21 +0000
treeherderautoland@be52a545a986 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdroeh, dimi, snorp
bugs1660241
milestone83.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 1660241 - Add GeckoRuntimeSetting for SafeBrowsing provider. r=droeh,dimi,snorp Differential Revision: https://phabricator.services.mozilla.com/D91200
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentBlockingControllerTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlocking.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
modules/libpref/init/all.js
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -272,17 +272,19 @@ package org.mozilla.geckoview {
     method public int getClearColor();
     method @Nullable public Runnable getFirstPaintCallback();
     method public void removeDrawCallback(@NonNull Runnable);
     method public void setClearColor(int);
     method public void setFirstPaintCallback(@Nullable Runnable);
   }
 
   @AnyThread public class ContentBlocking {
-    ctor public ContentBlocking();
+    ctor protected ContentBlocking();
+    field public static final ContentBlocking.SafeBrowsingProvider GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER;
+    field public static final ContentBlocking.SafeBrowsingProvider GOOGLE_SAFE_BROWSING_PROVIDER;
   }
 
   public static class ContentBlocking.AntiTracking {
     ctor protected AntiTracking();
     field public static final int AD = 2;
     field public static final int ANALYTIC = 4;
     field public static final int CONTENT = 16;
     field public static final int CRYPTOMINING = 64;
@@ -338,42 +340,84 @@ package org.mozilla.geckoview {
     field public static final int DEFAULT = 15360;
     field public static final int HARMFUL = 4096;
     field public static final int MALWARE = 1024;
     field public static final int NONE = 0;
     field public static final int PHISHING = 8192;
     field public static final int UNWANTED = 2048;
   }
 
+  @AnyThread public static class ContentBlocking.SafeBrowsingProvider extends RuntimeSettings {
+    method @NonNull public static ContentBlocking.SafeBrowsingProvider.Builder from(@NonNull ContentBlocking.SafeBrowsingProvider);
+    method @Nullable public String getAdvisoryName();
+    method @Nullable public String getAdvisoryUrl();
+    method @Nullable public Boolean getDataSharingEnabled();
+    method @Nullable public String getDataSharingUrl();
+    method @Nullable public String getGetHashUrl();
+    method @NonNull public String[] getLists();
+    method @NonNull public String getName();
+    method @Nullable public String getReportMalwareMistakeUrl();
+    method @Nullable public String getReportPhishingMistakeUrl();
+    method @Nullable public String getReportUrl();
+    method @Nullable public String getUpdateUrl();
+    method @Nullable public String getVersion();
+    method @NonNull public static ContentBlocking.SafeBrowsingProvider.Builder withName(@NonNull String);
+    field public static final Parcelable.Creator<ContentBlocking.SafeBrowsingProvider> CREATOR;
+  }
+
+  @AnyThread public static class ContentBlocking.SafeBrowsingProvider.Builder {
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder advisoryName(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder advisoryUrl(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider build();
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder dataSharingEnabled(boolean);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder dataSharingUrl(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder getHashUrl(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder lists(@NonNull String...);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder reportMalwareMistakeUrl(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder reportPhishingMistakeUrl(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder reportUrl(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder updateUrl(@NonNull String);
+    method @NonNull public ContentBlocking.SafeBrowsingProvider.Builder version(@NonNull String);
+  }
+
   @AnyThread public static class ContentBlocking.Settings extends RuntimeSettings {
     method public int getAntiTrackingCategories();
     method public int getCookieBehavior();
     method public int getCookieLifetime();
     method public boolean getCookiePurging();
     method public int getEnhancedTrackingProtectionLevel();
     method public int getSafeBrowsingCategories();
+    method @NonNull public String[] getSafeBrowsingMalwareTable();
+    method @NonNull public String[] getSafeBrowsingPhishingTable();
+    method @NonNull public Collection<ContentBlocking.SafeBrowsingProvider> getSafeBrowsingProviders();
     method public boolean getStrictSocialTrackingProtection();
     method @NonNull public ContentBlocking.Settings setAntiTracking(int);
     method @NonNull public ContentBlocking.Settings setCookieBehavior(int);
     method @NonNull public ContentBlocking.Settings setCookieLifetime(int);
     method @NonNull public ContentBlocking.Settings setCookiePurging(boolean);
     method @NonNull public ContentBlocking.Settings setEnhancedTrackingProtectionLevel(int);
     method @NonNull public ContentBlocking.Settings setSafeBrowsing(int);
+    method @NonNull public ContentBlocking.Settings setSafeBrowsingMalwareTable(@NonNull String...);
+    method @NonNull public ContentBlocking.Settings setSafeBrowsingPhishingTable(@NonNull String...);
+    method @NonNull public ContentBlocking.Settings setSafeBrowsingProviders(@NonNull ContentBlocking.SafeBrowsingProvider...);
     method @NonNull public ContentBlocking.Settings setStrictSocialTrackingProtection(boolean);
     field public static final Parcelable.Creator<ContentBlocking.Settings> CREATOR;
   }
 
   @AnyThread public static class ContentBlocking.Settings.Builder extends RuntimeSettings.Builder {
     ctor public Builder();
     method @NonNull public ContentBlocking.Settings.Builder antiTracking(int);
     method @NonNull public ContentBlocking.Settings.Builder cookieBehavior(int);
     method @NonNull public ContentBlocking.Settings.Builder cookieLifetime(int);
     method @NonNull public ContentBlocking.Settings.Builder cookiePurging(boolean);
     method @NonNull public ContentBlocking.Settings.Builder enhancedTrackingProtectionLevel(int);
     method @NonNull public ContentBlocking.Settings.Builder safeBrowsing(int);
+    method @NonNull public ContentBlocking.Settings.Builder safeBrowsingMalwareTable(@NonNull String[]);
+    method @NonNull public ContentBlocking.Settings.Builder safeBrowsingPhishingTable(@NonNull String[]);
+    method @NonNull public ContentBlocking.Settings.Builder safeBrowsingProviders(@NonNull ContentBlocking.SafeBrowsingProvider...);
     method @NonNull public ContentBlocking.Settings.Builder strictSocialTrackingProtection(boolean);
     method @NonNull protected ContentBlocking.Settings newSettings(@Nullable ContentBlocking.Settings);
   }
 
   @AnyThread public class ContentBlockingController {
     ctor public ContentBlockingController();
     method @UiThread public void addException(@NonNull GeckoSession);
     method @UiThread @NonNull public GeckoResult<Boolean> checkException(@NonNull GeckoSession);
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentBlockingControllerTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentBlockingControllerTest.kt
@@ -116,16 +116,121 @@ class ContentBlockingControllerTest : Ba
     @Test
     fun trackingProtectionException() {
         // disable test on debug for frequently failing #Bug 1580223
         assumeThat(sessionRule.env.isDebugBuild, equalTo(false))
 
         testTrackingProtectionException(mainSession.settings)
     }
 
+    @Test
+    // Smoke test for safe browsing settings, most testing is through platform tests
+    fun safeBrowsingSettings() {
+        val contentBlocking = sessionRule.runtime.settings.contentBlocking
+
+        val google = contentBlocking.safeBrowsingProviders.first { it.name == "google" }
+        val google4 = contentBlocking.safeBrowsingProviders.first { it.name == "google4" }
+
+        // Let's make sure the initial value of safeBrowsingProviders is correct
+        assertThat("Expected number of default providers",
+                contentBlocking.safeBrowsingProviders.size,
+                equalTo(2))
+        assertThat("Google legacy provider is present", google, notNullValue())
+        assertThat("Google provider is present", google4, notNullValue())
+
+        // Checks that the default provider values make sense
+        assertThat("Default provider values are sensible",
+            google.getHashUrl, containsString("/safebrowsing-dummy/"))
+        assertThat("Default provider values are sensible",
+                google.advisoryUrl, startsWith("https://developers.google.com/"))
+        assertThat("Default provider values are sensible",
+                google4.getHashUrl, containsString("/safebrowsing4-dummy/"))
+        assertThat("Default provider values are sensible",
+                google4.updateUrl, containsString("/safebrowsing4-dummy/"))
+        assertThat("Default provider values are sensible",
+                google4.dataSharingUrl, startsWith("https://safebrowsing.googleapis.com/"))
+
+        // Checks that the pref value is also consistent with the runtime settings
+        val originalPrefs = sessionRule.getPrefs(
+                "browser.safebrowsing.provider.google4.updateURL",
+                "browser.safebrowsing.provider.google4.gethashURL",
+                "browser.safebrowsing.provider.google4.lists"
+        )
+
+        assertThat("Initial prefs value is correct",
+                originalPrefs[0] as String, equalTo(google4.updateUrl))
+        assertThat("Initial prefs value is correct",
+                originalPrefs[1] as String, equalTo(google4.getHashUrl))
+        assertThat("Initial prefs value is correct",
+                originalPrefs[2] as String, equalTo(google4.lists.joinToString(",")))
+
+        // Makes sure we can override a default value
+        val override = ContentBlocking.SafeBrowsingProvider
+                .from(ContentBlocking.GOOGLE_SAFE_BROWSING_PROVIDER)
+                .updateUrl("http://test-update-url.com")
+                .getHashUrl("http://test-get-hash-url.com")
+                .build()
+
+        // ... and that we can add a custom provider
+        val custom = ContentBlocking.SafeBrowsingProvider
+                .withName("custom-provider")
+                .updateUrl("http://test-custom-update-url.com")
+                .getHashUrl("http://test-custom-get-hash-url.com")
+                .lists("a", "b", "c")
+                .build()
+
+        assertThat("Override value is correct",
+                override.updateUrl, equalTo("http://test-update-url.com"))
+        assertThat("Override value is correct",
+                override.getHashUrl, equalTo("http://test-get-hash-url.com"))
+
+        assertThat("Custom provider value is correct",
+                custom.updateUrl, equalTo("http://test-custom-update-url.com"))
+        assertThat("Custom provider value is correct",
+                custom.getHashUrl, equalTo("http://test-custom-get-hash-url.com"))
+        assertThat("Custom provider value is correct",
+                custom.lists, equalTo(arrayOf("a", "b", "c")))
+
+        contentBlocking.setSafeBrowsingProviders(override, custom)
+
+        val prefs = sessionRule.getPrefs(
+                "browser.safebrowsing.provider.google4.updateURL",
+                "browser.safebrowsing.provider.google4.gethashURL",
+                "browser.safebrowsing.provider.custom-provider.updateURL",
+                "browser.safebrowsing.provider.custom-provider.gethashURL",
+                "browser.safebrowsing.provider.custom-provider.lists")
+
+        assertThat("Pref value is set correctly",
+                prefs[0] as String, equalTo("http://test-update-url.com"))
+        assertThat("Pref value is set correctly",
+                prefs[1] as String, equalTo("http://test-get-hash-url.com"))
+        assertThat("Pref value is set correctly",
+                prefs[2] as String, equalTo("http://test-custom-update-url.com"))
+        assertThat("Pref value is set correctly",
+                prefs[3] as String, equalTo("http://test-custom-get-hash-url.com"))
+        assertThat("Pref value is set correctly",
+                prefs[4] as String, equalTo("a,b,c"))
+
+        // Restore defaults
+        contentBlocking.setSafeBrowsingProviders(google, google4)
+
+        // Checks that after restoring the providers the prefs get updated
+        val restoredPrefs = sessionRule.getPrefs(
+                "browser.safebrowsing.provider.google4.updateURL",
+                "browser.safebrowsing.provider.google4.gethashURL",
+                "browser.safebrowsing.provider.google4.lists")
+
+        assertThat("Restored prefs value is correct",
+                restoredPrefs[0] as String, equalTo(originalPrefs[0]))
+        assertThat("Restored prefs value is correct",
+                restoredPrefs[1] as String, equalTo(originalPrefs[1]))
+        assertThat("Restored prefs value is correct",
+                restoredPrefs[2] as String, equalTo(originalPrefs[2]))
+    }
+
     @GeckoSessionTestRule.Setting(key = GeckoSessionTestRule.Setting.Key.USE_TRACKING_PROTECTION, value = "true")
     @Test
     fun trackingProtectionExceptionRemoveByException() {
         // disable test on debug for frequently failing #Bug 1580223
         assumeThat(sessionRule.env.isDebugBuild, equalTo(false))
         val category = ContentBlocking.AntiTracking.TEST
         sessionRule.runtime.settings.contentBlocking.setAntiTracking(category)
         sessionRule.session.loadTestPath(TRACKERS_PATH)
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.geckoview.test;
 
 import org.mozilla.geckoview.AllowOrDeny;
+import org.mozilla.geckoview.ContentBlocking;
 import org.mozilla.geckoview.GeckoDisplay;
 import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoRuntimeSettings;
 import org.mozilla.geckoview.WebExtension;
@@ -282,18 +283,33 @@ public class TestRunnerActivity extends 
                     .displayDensityOverride(1.0f)
                     .remoteDebuggingEnabled(true);
 
             final Bundle extras = intent.getExtras();
             if (extras != null) {
                 runtimeSettingsBuilder.extras(extras);
             }
 
+            final ContentBlocking.SafeBrowsingProvider googleLegacy = ContentBlocking.SafeBrowsingProvider
+                    .from(ContentBlocking.GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER)
+                    .getHashUrl("http://mochi.test:8888/safebrowsing-dummy/gethash")
+                    .updateUrl("http://mochi.test:8888/safebrowsing-dummy/update")
+                    .build();
+
+            final ContentBlocking.SafeBrowsingProvider google = ContentBlocking.SafeBrowsingProvider
+                    .from(ContentBlocking.GOOGLE_SAFE_BROWSING_PROVIDER)
+                    .getHashUrl("http://mochi.test:8888/safebrowsing4-dummy/gethash")
+                    .updateUrl("http://mochi.test:8888/safebrowsing4-dummy/update")
+                    .build();
+
             runtimeSettingsBuilder
                     .consoleOutput(true)
+                    .contentBlocking(new ContentBlocking.Settings.Builder()
+                        .safeBrowsingProviders(google, googleLegacy)
+                        .build())
                     .crashHandler(TestCrashHandler.class);
 
             sRuntime = GeckoRuntime.create(this, runtimeSettingsBuilder.build());
 
             webExtensionController().setDebuggerDelegate(new WebExtensionController.DebuggerDelegate() {
                 @Override
                 public void onExtensionListUpdated() {
                     refreshExtensionList();
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java
@@ -1,16 +1,19 @@
 package org.mozilla.geckoview.test.util;
 
+import org.mozilla.geckoview.ContentBlocking;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoRuntimeSettings;
 import org.mozilla.geckoview.RuntimeTelemetry;
 import org.mozilla.geckoview.WebExtension;
 import org.mozilla.geckoview.test.TestCrashHandler;
 
+import static org.mozilla.geckoview.ContentBlocking.SafeBrowsingProvider;
+
 import android.os.Looper;
 import android.os.Process;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.test.platform.app.InstrumentationRegistry;
 import android.util.Log;
 
@@ -138,18 +141,33 @@ public class RuntimeCreator {
     };
 
     @UiThread
     public static GeckoRuntime getRuntime() {
         if (sRuntime != null) {
             return sRuntime;
         }
 
+        final SafeBrowsingProvider googleLegacy = SafeBrowsingProvider
+                .from(ContentBlocking.GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER)
+                .getHashUrl("http://mochi.test:8888/safebrowsing-dummy/gethash")
+                .updateUrl("http://mochi.test:8888/safebrowsing-dummy/update")
+                .build();
+
+        final SafeBrowsingProvider google = SafeBrowsingProvider
+                .from(ContentBlocking.GOOGLE_SAFE_BROWSING_PROVIDER)
+                .getHashUrl("http://mochi.test:8888/safebrowsing4-dummy/gethash")
+                .updateUrl("http://mochi.test:8888/safebrowsing4-dummy/update")
+                .build();
+
         final GeckoRuntimeSettings runtimeSettings = new GeckoRuntimeSettings.Builder()
                 .useMultiprocess(env.isMultiprocess())
+                .contentBlocking(new ContentBlocking.Settings.Builder()
+                        .safeBrowsingProviders(googleLegacy, google)
+                        .build())
                 .arguments(new String[]{"-purgecaches"})
                 .extras(InstrumentationRegistry.getArguments())
                 .remoteDebuggingEnabled(true)
                 .consoleOutput(true)
                 .crashHandler(TestCrashHandler.class)
                 .telemetryDelegate(sRuntimeTelemetryProxy)
                 .build();
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -94,18 +94,21 @@ public final class GeckoLoader {
     private static String escapeDoubleQuotes(final String str) {
         return str.replaceAll("\"", "\\\"");
     }
 
     private static void setupInitialPrefs(final Map<String, Object> prefs) {
         if (prefs != null) {
             final StringBuilder prefsEnv = new StringBuilder("MOZ_DEFAULT_PREFS=");
             for (final String key : prefs.keySet()) {
+                final Object value = prefs.get(key);
+                if (value == null) {
+                    continue;
+                }
                 prefsEnv.append(String.format("pref(\"%s\",", escapeDoubleQuotes(key)));
-                final Object value = prefs.get(key);
                 if (value instanceof String) {
                     prefsEnv.append(String.format("\"%s\"", escapeDoubleQuotes(value.toString())));
                 } else if (value instanceof Boolean) {
                     prefsEnv.append((Boolean)value ? "true" : "false");
                 } else {
                     prefsEnv.append(value.toString());
                 }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlocking.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlocking.java
@@ -3,16 +3,20 @@
  * 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/. */
 
 package org.mozilla.geckoview;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 import android.os.Parcelable;
 import android.os.Parcel;
 import androidx.annotation.AnyThread;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -21,27 +25,125 @@ import android.text.TextUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 
 /**
  * Content Blocking API to hold and control anti-tracking, cookie and Safe
  * Browsing settings.
  */
 @AnyThread
 public class ContentBlocking {
+    /**
+     * {@link SafeBrowsingProvider} configuration for Google's legacy SafeBrowsing server.
+     */
+    public final static SafeBrowsingProvider GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER =
+            SafeBrowsingProvider.withName("google")
+                    .version("2.2")
+                    .lists("goog-badbinurl-shavar",
+                           "goog-downloadwhite-digest256",
+                           "goog-phish-shavar",
+                           "googpub-phish-shavar",
+                           "goog-malware-shavar",
+                           "goog-unwanted-shavar")
+                    .updateUrl("https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2&key=%GOOGLE_SAFEBROWSING_API_KEY%")
+                    .getHashUrl("https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2")
+                    .reportUrl("https://safebrowsing.google.com/safebrowsing/diagnostic?site=")
+                    .reportPhishingMistakeUrl("https://%LOCALE%.phish-error.mozilla.com/?url=")
+                    .reportMalwareMistakeUrl("https://%LOCALE%.malware-error.mozilla.com/?url=")
+                    .advisoryUrl("https://developers.google.com/safe-browsing/v4/advisory")
+                    .advisoryName("Google Safe Browsing")
+                    .build();
+
+    /**
+     * {@link SafeBrowsingProvider} configuration for Google's SafeBrowsing server.
+     */
+    public final static SafeBrowsingProvider GOOGLE_SAFE_BROWSING_PROVIDER =
+            SafeBrowsingProvider.withName("google4")
+                    .version("4")
+                    .lists("goog-badbinurl-proto",
+                           "goog-downloadwhite-proto",
+                           "goog-phish-proto",
+                           "googpub-phish-proto",
+                           "goog-malware-proto",
+                           "goog-unwanted-proto",
+                           "goog-harmful-proto",
+                           "goog-passwordwhite-proto")
+                    .updateUrl("https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST")
+                    .getHashUrl("https://safebrowsing.googleapis.com/v4/fullHashes:find?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST")
+                    .reportUrl("https://safebrowsing.google.com/safebrowsing/diagnostic?site=")
+                    .reportPhishingMistakeUrl("https://%LOCALE%.phish-error.mozilla.com/?url=")
+                    .reportMalwareMistakeUrl("https://%LOCALE%.malware-error.mozilla.com/?url=")
+                    .advisoryUrl("https://developers.google.com/safe-browsing/v4/advisory")
+                    .advisoryName("Google Safe Browsing")
+                    .dataSharingUrl("https://safebrowsing.googleapis.com/v4/threatHits?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST")
+                    .dataSharingEnabled(false)
+                    .build();
+
+    // This class shouldn't be instantiated
+    protected ContentBlocking() {}
+
     @AnyThread
     public static class Settings extends RuntimeSettings {
+        private final Map<String, SafeBrowsingProvider> mSafeBrowsingProviders = new HashMap<>();
+
+        private final static SafeBrowsingProvider[] DEFAULT_PROVIDERS = {
+            ContentBlocking.GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER,
+            ContentBlocking.GOOGLE_SAFE_BROWSING_PROVIDER
+        };
+
         @AnyThread
         public static class Builder
                 extends RuntimeSettings.Builder<Settings> {
             @Override
             protected @NonNull Settings newSettings(final @Nullable Settings settings) {
                 return new Settings(settings);
             }
 
             /**
+             * Set custom safe browsing providers.
+             *
+             * @param providers one or more custom providers.
+             *
+             * @return This Builder instance.
+             * @see SafeBrowsingProvider
+             */
+            public @NonNull Builder safeBrowsingProviders(
+                    final @NonNull SafeBrowsingProvider... providers) {
+                getSettings().setSafeBrowsingProviders(providers);
+                return this;
+            }
+
+            /**
+             * Set the safe browsing table for phishing threats.
+             *
+             * @param safeBrowsingPhishingTable one or more lists for safe browsing phishing.
+             *
+             * @return This Builder instance.
+             * @see SafeBrowsingProvider
+             */
+            public @NonNull Builder safeBrowsingPhishingTable(
+                    final @NonNull String[] safeBrowsingPhishingTable) {
+                getSettings().setSafeBrowsingPhishingTable(safeBrowsingPhishingTable);
+                return this;
+            }
+
+            /**
+             * Set the safe browsing table for malware threats.
+             *
+             * @param safeBrowsingMalwareTable one or more lists for safe browsing malware.
+             *
+             * @return This Builder instance.
+             * @see SafeBrowsingProvider
+             */
+            public @NonNull Builder safeBrowsingMalwareTable(
+                    final @NonNull String[] safeBrowsingMalwareTable) {
+                getSettings().setSafeBrowsingMalwareTable(safeBrowsingMalwareTable);
+                return this;
+            }
+
+            /**
              * Set anti-tracking categories.
              *
              * @param cat The categories of resources that should be blocked.
              *            Use one or more of the
              *            {@link ContentBlocking.AntiTracking} flags.
              * @return This Builder instance.
              */
             public @NonNull Builder antiTracking(final @CBAntiTracking int cat) {
@@ -163,16 +265,30 @@ public class ContentBlocking {
         /* package */ final Pref<Boolean> mCookiePurging = new Pref<Boolean>(
             "privacy.purge_trackers.enabled", false);
 
         /* package */ final Pref<Boolean> mEtpEnabled = new Pref<Boolean>(
             "privacy.trackingprotection.annotate_channels", false);
         /* package */ final Pref<Boolean> mEtpStrict = new Pref<Boolean>(
             "privacy.annotate_channels.strict_list.enabled", false);
 
+        /* package */ final Pref<String> mSafeBrowsingMalwareTable = new Pref<>(
+            "urlclassifier.malwareTable", ContentBlocking.listsToPref(
+                "goog-malware-proto",
+                "goog-unwanted-proto",
+                "moztest-harmful-simple",
+                "moztest-malware-simple",
+                "moztest-unwanted-simple"));
+        /* package */ final Pref<String> mSafeBrowsingPhishingTable = new Pref<>(
+            "urlclassifier.phishTable", ContentBlocking.listsToPref(
+                // In official builds, we are allowed to use Google's private phishing
+                // list (see bug 1288840).
+                BuildConfig.MOZILLA_OFFICIAL ? "goog-phish-proto" : "googpub-phish-proto",
+                "moztest-phish-simple"));
+
         /**
          * Construct default settings.
          */
         /* package */ Settings() {
             this(null /* settings */);
         }
 
         /**
@@ -190,23 +306,114 @@ public class ContentBlocking {
          * @param parent The parent settings used for nesting.
          * @param settings Copy from this settings.
          */
         /* package */ Settings(final @Nullable RuntimeSettings parent,
                                final @Nullable Settings settings) {
             super(parent);
 
             if (settings != null) {
-                updateSettings(settings);
+                updatePrefs(settings);
+            } else {
+                // Set default browsing providers
+                setSafeBrowsingProviders(DEFAULT_PROVIDERS);
+            }
+        }
+
+        @Override
+        protected void updatePrefs(final @NonNull RuntimeSettings settings) {
+            super.updatePrefs(settings);
+
+            final ContentBlocking.Settings source = (ContentBlocking.Settings) settings;
+            for (final SafeBrowsingProvider provider : source.mSafeBrowsingProviders.values()) {
+                mSafeBrowsingProviders.put(
+                        provider.getName(),
+                        new SafeBrowsingProvider(this, provider));
             }
         }
 
-        private void updateSettings(final @NonNull Settings settings) {
-            // We only have pref settings here.
-            updatePrefs(settings);
+        /**
+         * Get the collection of {@link SafeBrowsingProvider} for this runtime.
+         *
+         * @return an unmodifiable collection of {@link SafeBrowsingProvider}
+         * @see SafeBrowsingProvider
+         */
+        public @NonNull Collection<SafeBrowsingProvider> getSafeBrowsingProviders() {
+            return Collections.unmodifiableCollection(mSafeBrowsingProviders.values());
+        }
+
+        /**
+         * Sets the collection of {@link SafeBrowsingProvider} for this runtime.
+         *
+         * By default the collection is composed of
+         * {@link ContentBlocking#GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER} and
+         * {@link ContentBlocking#GOOGLE_SAFE_BROWSING_PROVIDER}.
+         *
+         * @param providers {@link SafeBrowsingProvider} instances for this runtime.
+         *
+         * @return the {@link Settings} instance.
+         * @see SafeBrowsingProvider
+         */
+        public @NonNull Settings setSafeBrowsingProviders(
+                final @NonNull SafeBrowsingProvider... providers) {
+            mSafeBrowsingProviders.clear();
+
+            for (final SafeBrowsingProvider provider : providers) {
+                mSafeBrowsingProviders.put(
+                        provider.getName(),
+                        new SafeBrowsingProvider(this, provider));
+            }
+
+            return this;
+        }
+
+        /**
+         * Get the table for SafeBrowsing Phishing. The identifiers present in this table must
+         * match one of the identifiers present in {@link SafeBrowsingProvider#getLists}.
+         *
+         * @return an array of identifiers for SafeBrowsing's Phishing feature
+         * @see SafeBrowsingProvider.Builder#lists
+         */
+        public @NonNull String[] getSafeBrowsingPhishingTable() {
+            return ContentBlocking.prefToLists(mSafeBrowsingPhishingTable.get());
+        }
+
+        /**
+         * Sets the table for SafeBrowsing Phishing.
+         *
+         * @param table an array of identifiers for SafeBrowsing's Phishing feature.
+         * @return this {@link Settings} instance.
+         * @see SafeBrowsingProvider.Builder#lists
+         */
+        public @NonNull Settings setSafeBrowsingPhishingTable(final @NonNull String... table) {
+            mSafeBrowsingPhishingTable.commit(ContentBlocking.listsToPref(table));
+            return this;
+        }
+
+        /**
+         * Get the table for SafeBrowsing Malware. The identifiers present in this table must
+         * match one of the identifiers present in {@link SafeBrowsingProvider#getLists}.
+         *
+         * @return an array of identifiers for SafeBrowsing's Malware feature
+         * @see SafeBrowsingProvider.Builder#lists
+         */
+        public @NonNull String[] getSafeBrowsingMalwareTable() {
+            return ContentBlocking.prefToLists(mSafeBrowsingMalwareTable.get());
+        }
+
+        /**
+         * Sets the table for SafeBrowsing Malware.
+         *
+         * @param table an array of identifiers for SafeBrowsing's Malware feature.
+         * @return this {@link Settings} instance.
+         * @see SafeBrowsingProvider.Builder#lists
+         */
+        public @NonNull Settings setSafeBrowsingMalwareTable(final @NonNull String... table) {
+            mSafeBrowsingMalwareTable.commit(ContentBlocking.listsToPref(table));
+            return this;
         }
 
         /**
          * Set anti-tracking categories.
          *
          * @param cat The categories of resources that should be blocked.
          *            Use one or more of the
          *            {@link ContentBlocking.AntiTracking} flags.
@@ -396,16 +603,518 @@ public class ContentBlocking {
 
                     @Override
                     public Settings[] newArray(final int size) {
                         return new Settings[size];
                     }
                 };
     }
 
+    /**
+     * Holds configuration for a SafeBrowsing provider. <br><br>
+     *
+     * This class can be used to modify existing configuration for SafeBrowsing providers
+     * or to add a custom SafeBrowsing provider to the app. <br><br>
+     *
+     * Default configuration for Google's SafeBrowsing servers can be found at
+     * {@link ContentBlocking#GOOGLE_SAFE_BROWSING_PROVIDER} and
+     * {@link ContentBlocking#GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER}. <br><br>
+     *
+     * This class is immutable, once constructed its values cannot be changed. <br><br>
+     *
+     * You can, however, use the {@link #from} method to build upon an
+     * existing configuration. For example to override the Google's server configuration,
+     * you can do the following: <br>
+     *
+     * <pre><code>
+     *     SafeBrowsingProvider override = SafeBrowsingProvider
+     *         .from(ContentBlocking.GOOGLE_SAFE_BROWSING_PROVIDER)
+     *         .getHashUrl("http://my-custom-server.com/...")
+     *         .updateUrl("http://my-custom-server.com/...")
+     *         .build();
+     *
+     *     runtime.getContentBlocking().setSafeBrowsingProviders(override);
+     * </code></pre>
+     *
+     * This will override the configuration. <br><br>
+     *
+     * You can also add a custom SafeBrowsing provider using the {@link #withName} method. For
+     * example, to add a custom provider that provides the list
+     * <code>testprovider-phish-digest256</code> do the following: <br>
+     *
+     * <pre><code>
+     *     SafeBrowsingProvider custom = SafeBrowsingProvider
+     *         .withName("custom-provider")
+     *         .version("2.2")
+     *         .lists("testprovider-phish-digest256")
+     *         .updateUrl("http://my-custom-server2.com/...")
+     *         .getHashUrl("http://my-custom-server2.com/...")
+     *         .build();
+     * </code></pre>
+     *
+     * And then add the custom provider (adding optionally existing providers): <br>
+     *
+     * <pre><code>
+     *     runtime.getContentBlocking().setSafeBrowsingProviders(
+     *         custom,
+     *         // Add this if you want to keep the existing configuration too.
+     *         ContentBlocking.GOOGLE_SAFE_BROWSING_PROVIDER,
+     *         ContentBlocking.GOOGLE_LEGACY_SAFE_BROWSING_PROVIDER);
+     * </code></pre>
+     *
+     * And set the list in the phishing configuration <br>
+     *
+     * <pre><code>
+     *     runtime.getContentBlocking().setSafeBrowsingPhishingTable(
+     *          "testprovider-phish-digest256",
+     *          // Existing configuration
+     *          "goog-phish-proto");
+     * </code></pre>
+     *
+     * Note that any list present in the phishing or malware tables need to appear in one
+     * safe browsing provider's {@link #getLists} property.
+     *
+     * See also <a href="https://developers.google.com/safe-browsing/v4">safe-browsing/v4</a>.
+     */
+    @AnyThread
+    public static class SafeBrowsingProvider extends RuntimeSettings {
+        final static private String ROOT = "browser.safebrowsing.provider.";
+
+        final private String mName;
+
+        /* package */ final Pref<String> mVersion;
+        /* package */ final Pref<String> mLists;
+        /* package */ final Pref<String> mUpdateUrl;
+        /* package */ final Pref<String> mGetHashUrl;
+        /* package */ final Pref<String> mReportUrl;
+        /* package */ final Pref<String> mReportPhishingMistakeUrl;
+        /* package */ final Pref<String> mReportMalwareMistakeUrl;
+        /* package */ final Pref<String> mAdvisoryUrl;
+        /* package */ final Pref<String> mAdvisoryName;
+        /* package */ final Pref<String> mDataSharingUrl;
+        /* package */ final Pref<Boolean> mDataSharingEnabled;
+
+        /**
+         * Creates a {@link SafeBrowsingProvider.Builder} for a provider with the given name.
+         *
+         * Note: the <code>mozilla</code> name is reserved for internal use, and this method
+         * will throw if you attempt to build a provider with that name.
+         *
+         * @param name The name of the provider.
+         * @return a {@link Builder} instance that can be used to build a provider.
+         * @throws IllegalArgumentException if this method is called with
+         *         <code>name="mozilla"</code>
+         */
+        @NonNull
+        public static Builder withName(final @NonNull String name) {
+            if ("mozilla".equals(name)) {
+                throw new IllegalArgumentException(
+                        "The 'mozilla' name is reserved for internal use.");
+            }
+            return new Builder(name);
+        }
+
+        /**
+         * Creates a {@link SafeBrowsingProvider.Builder} based on the given provider.
+         *
+         * All properties not otherwise specified will be copied from the provider given in input.
+         *
+         * @param provider The source provider for this builder.
+         *
+         * @return a {@link Builder} instance that can be used to create a configuration based
+         *         on the builder in input.
+         */
+        @NonNull
+        public static Builder from(final @NonNull SafeBrowsingProvider provider) {
+            return new Builder(provider);
+        }
+
+        @AnyThread
+        public static class Builder {
+            final SafeBrowsingProvider mProvider;
+
+            private Builder(final String name) {
+                mProvider = new SafeBrowsingProvider(name);
+            }
+
+            private Builder(final SafeBrowsingProvider source) {
+                mProvider = new SafeBrowsingProvider(source);
+            }
+
+            /**
+             * Sets the SafeBrowsing protocol session for this provider.
+             *
+             * @param version the version strong, e.g. "2.2" or "4".
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder version(final @NonNull String version) {
+                mProvider.mVersion.set(version);
+                return this;
+            }
+
+            /**
+             * Sets the lists provided by this provider.
+             *
+             * @param lists one or more lists for this provider, e.g. "goog-malware-proto",
+             *                 "goog-unwanted-proto"
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder lists(final @NonNull String... lists) {
+                mProvider.mLists.set(ContentBlocking.listsToPref(lists));
+                return this;
+            }
+
+            /**
+             * Sets the url that will be used to update the threat list for this provider.
+             *
+             * See also
+             * <a href="https://developers.google.com/safe-browsing/v4/reference/rest/v4/threatListUpdates/fetch">
+             *     v4/threadListUpdates/fetch
+             * </a>.
+             *
+             * @param updateUrl the update url endpoint for this provider
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder updateUrl(final @NonNull String updateUrl) {
+                mProvider.mUpdateUrl.set(updateUrl);
+                return this;
+            }
+
+            /**
+             * Sets the url that will be used to get the full hashes that match a partial hash.
+             *
+             * See also
+             * <a href="https://developers.google.com/safe-browsing/v4/reference/rest/v4/fullHashes/find">
+             *     v4/fullHashes/find
+             * </a>.
+             *
+             * @param getHashUrl the gethash url endpoint for this provider
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder getHashUrl(final @NonNull String getHashUrl) {
+                mProvider.mGetHashUrl.set(getHashUrl);
+                return this;
+            }
+
+            /**
+             * Set the url that will be used to report a url to the SafeBrowsing provider.
+             *
+             * @param reportUrl the url endpoint to report a url to this provider.
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder reportUrl(final @NonNull String reportUrl) {
+                mProvider.mReportUrl.set(reportUrl);
+                return this;
+            }
+
+            /**
+             * Set the url that will be used to report a url mistakenly reported as Phishing
+             * to the SafeBrowsing provider.
+             *
+             * @param reportPhishingMistakeUrl
+             *        the url endpoint to report a url to this provider.
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder reportPhishingMistakeUrl(
+                    final @NonNull String reportPhishingMistakeUrl) {
+                mProvider.mReportPhishingMistakeUrl.set(reportPhishingMistakeUrl);
+                return this;
+            }
+
+            /**
+             * Set the url that will be used to report a url mistakenly reported as Malware
+             * to the SafeBrowsing provider.
+             *
+             * @param reportMalwareMistakeUrl
+             *        the url endpoint to report a url to this provider.
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder reportMalwareMistakeUrl(
+                    final @NonNull String reportMalwareMistakeUrl) {
+                mProvider.mReportMalwareMistakeUrl.set(reportMalwareMistakeUrl);
+                return this;
+            }
+
+            /**
+             * Set the url that will be used to give a general advisory about this
+             * SafeBrowsing provider.
+             *
+             * @param advisoryUrl
+             *        the adivisory page url for this provider.
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder advisoryUrl(final @NonNull String advisoryUrl) {
+                mProvider.mAdvisoryUrl.set(advisoryUrl);
+                return this;
+            }
+
+            /**
+             * Set the advisory name for this provider.
+             *
+             * @param advisoryName
+             *        the adivisory name for this provider.
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder advisoryName(final @NonNull String advisoryName) {
+                mProvider.mAdvisoryName.set(advisoryName);
+                return this;
+            }
+
+            /**
+             * Set url to share threat data to the provider, if enabled by
+             *  {@link #dataSharingEnabled}.
+             *
+             * @param dataSharingUrl the url endpoint
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder dataSharingUrl(final @NonNull String dataSharingUrl) {
+                mProvider.mDataSharingUrl.set(dataSharingUrl);
+                return this;
+            }
+
+            /**
+             * Set whether to share threat data with the provider, off by default.
+             *
+             * @param dataSharingEnabled <code>true</code> if the browser should share threat
+             *                           data with the provider.
+             * @return this {@link Builder} instance.
+             */
+            public @NonNull Builder dataSharingEnabled(final boolean dataSharingEnabled) {
+                mProvider.mDataSharingEnabled.set(dataSharingEnabled);
+                return this;
+            }
+
+            /**
+             * Build the {@link SafeBrowsingProvider} based on this {@link Builder} instance.
+             * @return thie {@link SafeBrowsingProvider} instance.
+             */
+            public @NonNull SafeBrowsingProvider build() {
+                return new SafeBrowsingProvider(mProvider);
+            }
+        }
+
+        /* package */ SafeBrowsingProvider(final SafeBrowsingProvider source) {
+            this(/* name */ null, /* parent */ null, source);
+        }
+
+        /* package */ SafeBrowsingProvider(final RuntimeSettings parent,
+                                           final SafeBrowsingProvider source) {
+            this(/* name */ null, parent, source);
+        }
+
+        /* package */ SafeBrowsingProvider(final String name) {
+            this(name, /* parent */ null, /* source */ null);
+        }
+
+        /* package */ SafeBrowsingProvider(final String name,
+                                           final RuntimeSettings parent,
+                                           final SafeBrowsingProvider source) {
+            super(parent);
+
+            if (name != null) {
+                mName = name;
+            } else if (source != null) {
+                mName = source.mName;
+            } else {
+                throw new IllegalArgumentException("Either name or source must be non-null");
+            }
+
+            mVersion = new Pref<>(ROOT + mName + ".pver", null);
+            mLists = new Pref<>(ROOT + mName + ".lists", null);
+            mUpdateUrl = new Pref<>(ROOT + mName + ".updateURL", null);
+            mGetHashUrl = new Pref<>(ROOT + mName + ".gethashURL", null);
+            mReportUrl = new Pref<>(ROOT + mName + ".reportURL", null);
+            mReportPhishingMistakeUrl = new Pref<>(ROOT + mName + ".reportPhishMistakeURL", null);
+            mReportMalwareMistakeUrl = new Pref<>(ROOT + mName + ".reportMalwareMistakeURL", null);
+            mAdvisoryUrl = new Pref<>(ROOT + mName + ".advisoryURL", null);
+            mAdvisoryName = new Pref<>(ROOT + mName + ".advisoryName", null);
+            mDataSharingUrl = new Pref<>(ROOT + mName + ".dataSharingURL", null);
+            mDataSharingEnabled = new Pref<>(ROOT + mName + ".dataSharing.enabled", false);
+
+            if (source != null) {
+                updatePrefs(source);
+            }
+        }
+
+        /**
+         * Get the name of this provider.
+         *
+         * @return a string containing the name.
+         */
+        public @NonNull String getName() {
+            return mName;
+        }
+
+        /**
+         * Get the version for this provider.
+         *
+         * @return a string representing the version, e.g. "2.2" or "4".
+         */
+        public @Nullable String getVersion() {
+            return mVersion.get();
+        }
+
+        /**
+         * Get the lists provided by this provider.
+         *
+         * @return an array of string identifiers for the lists
+         */
+        public @NonNull String[] getLists() {
+            return ContentBlocking.prefToLists(mLists.get());
+        }
+
+        /**
+         * Get the url that will be used to update the threat list for this provider.
+         *
+         * See also
+         * <a href="https://developers.google.com/safe-browsing/v4/reference/rest/v4/threatListUpdates/fetch">
+         *     v4/threadListUpdates/fetch
+         * </a>.
+         *
+         * @return a string containing the URL.
+         */
+        public @Nullable String getUpdateUrl() {
+            return mUpdateUrl.get();
+        }
+
+        /**
+         * Get the url that will be used to get the full hashes that match a partial hash.
+         *
+         * See also
+         * <a href="https://developers.google.com/safe-browsing/v4/reference/rest/v4/fullHashes/find">
+         *     v4/fullHashes/find
+         * </a>.
+         *
+         * @return a string containing the URL.
+         */
+        public @Nullable String getGetHashUrl() {
+            return mGetHashUrl.get();
+        }
+
+        /**
+         * Get the url that will be used to report a url to the SafeBrowsing provider.
+         *
+         * @return a string containing the URL.
+         */
+        public @Nullable String getReportUrl() {
+            return mReportUrl.get();
+        }
+
+        /**
+         * Get the url that will be used to report a url mistakenly reported as Phishing
+         * to the SafeBrowsing provider.
+         *
+         * @return a string containing the URL.
+         */
+        public @Nullable String getReportPhishingMistakeUrl() {
+            return mReportPhishingMistakeUrl.get();
+        }
+
+        /**
+         * Get the url that will be used to report a url mistakenly reported as Malware
+         * to the SafeBrowsing provider.
+         *
+         * @return a string containing the URL.
+         */
+        public @Nullable String getReportMalwareMistakeUrl() {
+            return mReportMalwareMistakeUrl.get();
+        }
+
+        /**
+         * Get the url that will be used to give a general advisory about this
+         * SafeBrowsing provider.
+         *
+         * @return a string containing the URL.
+         */
+        public @Nullable String getAdvisoryUrl() {
+            return mAdvisoryUrl.get();
+        }
+
+        /**
+         * Get the advisory name for this provider.
+         *
+         * @return a string containing the URL.
+         */
+        public @Nullable String getAdvisoryName() {
+            return mAdvisoryName.get();
+        }
+
+        /**
+         * Get the url to share threat data to the provider, if enabled by
+         *  {@link #getDataSharingEnabled}.
+         *
+         * @return this {@link Builder} instance.
+         */
+        public @Nullable String getDataSharingUrl() {
+            return mDataSharingUrl.get();
+        }
+
+        /**
+         * Get whether to share threat data with the provider.
+         *
+         * @return <code>true</code> if the browser should whare threat data with the provider,
+         * <code>false</code> otherwise.
+         */
+        public @Nullable Boolean getDataSharingEnabled() {
+            return mDataSharingEnabled.get();
+        }
+
+        @Override // Parcelable
+        @AnyThread
+        public void writeToParcel(final Parcel out, final int flags) {
+            out.writeValue(mName);
+            super.writeToParcel(out, flags);
+        }
+
+        /**
+         * Creator instance for this class.
+         */
+        public static final Parcelable.Creator<SafeBrowsingProvider> CREATOR
+                = new Parcelable.Creator<SafeBrowsingProvider>() {
+                    @Override
+                    public SafeBrowsingProvider createFromParcel(final Parcel source) {
+                        final String name = (String) source.readValue(getClass().getClassLoader());
+                        final SafeBrowsingProvider settings = new SafeBrowsingProvider(name);
+                        settings.readFromParcel(source);
+                        return settings;
+                    }
+
+                    @Override
+                    public SafeBrowsingProvider[] newArray(final int size) {
+                        return new SafeBrowsingProvider[size];
+                    }
+                };
+    }
+
+    private static String listsToPref(final String... lists) {
+        final StringBuilder prefBuilder = new StringBuilder();
+
+        for (final String list : lists) {
+            if (list.contains(",")) {
+                // We use ',' as the separator, so the list name cannot contain it.
+                // Should never happen.
+                throw new IllegalArgumentException("List name cannot contain ',' character.");
+            }
+
+            prefBuilder.append(list);
+            prefBuilder.append(",");
+        }
+
+        // Remove trailing ","
+        if (lists.length > 0) {
+            prefBuilder.setLength(prefBuilder.length() - 1);
+        }
+
+        return prefBuilder.toString();
+    }
+
+    private static String[] prefToLists(final String pref) {
+        return pref != null ? pref.split(",") : new String[]{};
+    }
+
     public static class AntiTracking {
         public static final int NONE = 0;
 
         /**
          * Block advertisement trackers.
          */
         public static final int AD = 1 << 1;
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -18,19 +18,23 @@ exclude: true
   has been installed temporarily, e.g. when using web-ext.
   ([bug 1624410]({{bugzilla}}1624410))
 - ⚠️  Removing unsupported `MediaSession.Delegate.onPictureInPicture` for now.
   Also, [`MediaSession.Delegate.onMetadata`][83.2] is no longer dispatched for
   plain media elements.
   ([bug 1658937]({{bugzilla}}1658937))
 - Replaced android.util.ArrayMap with java.util.TreeMap in [`WebMessage`][65.13] to enable case-insensitive handling of the HTTP headers.
   ([bug 1666013]({{bugzilla}}1666013))
+- Added [`ContentBlocking.SafeBrowsingProvider`][83.3] to configure Safe
+  Browsing providers.
+  ([bug 1660241]({{bugzilla}}1660241))
 
 [83.1]: {{javadoc_uri}}/WebExtension.MetaData.html#temporary
 [83.2]: {{javadoc_uri}}/MediaSession.Delegate.html#onMetadata-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.MediaSession-org.mozilla.geckoview.MediaSession.Metadata-
+[83.3]: {{javadoc_uri}}/ContentBlocking.SafeBrowsingProvider.html
 
 ## v82
 - ⚠️  [`WebNotification.source`][79.2] is now `@Nullable` to account for
   WebExtension notifications which don't have a `source` field.
 - ⚠️ Deprecated [`ContentDelegate#onExternalResponse(GeckoSession, WebResponseInfo)`][82.1] with the intention of removing
   them in GeckoView v85. 
   ([bug 1530022]({{bugzilla}}1530022))
 - Added [`ContentDelegate#onExternalResponse(GeckoSession, WebResponse)`][82.2] to eliminate the need
@@ -810,9 +814,9 @@ to allow adding gecko profiler markers.
 [65.19]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.LoadRequest.html#isRedirect
 [65.20]: {{javadoc_uri}}/GeckoSession.html#LOAD_FLAGS_BYPASS_CLASSIFIER
 [65.21]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html
 [65.22]: {{javadoc_uri}}/GeckoSession.ContentDelegate.html#onContextMenu-org.mozilla.geckoview.GeckoSession-int-int-org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement-
 [65.23]: {{javadoc_uri}}/GeckoSession.FinderResult.html
 [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: {{javadoc_uri}}/GeckoResult.html
 
-[api-version]: 01fdaf45cfe4dc5974de314cbdb79584781eb282
+[api-version]: 2a562a8a5620ba5db90a9e7f59f6396eaeb4356f
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4168,16 +4168,19 @@ pref("browser.safebrowsing.downloads.ena
 pref("browser.safebrowsing.downloads.remote.enabled", true);
 pref("browser.safebrowsing.downloads.remote.timeout_ms", 15000);
 pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_SAFEBROWSING_API_KEY%");
 pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
 pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
 pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", true);
 pref("browser.safebrowsing.downloads.remote.block_uncommon",             true);
 
+// Android SafeBrowsing's configuration is in ContentBlocking.java, keep in sync.
+#ifndef MOZ_WIDGET_ANDROID
+
 // Google Safe Browsing provider (legacy)
 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_SAFEBROWSING_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?site=");
 pref("browser.safebrowsing.provider.google.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?url=");
 pref("browser.safebrowsing.provider.google.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?url=");
@@ -4192,16 +4195,18 @@ pref("browser.safebrowsing.provider.goog
 pref("browser.safebrowsing.provider.google4.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?site=");
 pref("browser.safebrowsing.provider.google4.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?url=");
 pref("browser.safebrowsing.provider.google4.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?url=");
 pref("browser.safebrowsing.provider.google4.advisoryURL", "https://developers.google.com/safe-browsing/v4/advisory");
 pref("browser.safebrowsing.provider.google4.advisoryName", "Google Safe Browsing");
 pref("browser.safebrowsing.provider.google4.dataSharingURL", "https://safebrowsing.googleapis.com/v4/threatHits?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST");
 pref("browser.safebrowsing.provider.google4.dataSharing.enabled", false);
 
+#endif // ifndef MOZ_WIDGET_ANDROID
+
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?url=");
 
 // Mozilla Safe Browsing provider (for tracking protection and plugin blocking)
 pref("browser.safebrowsing.provider.mozilla.pver", "2.2");
 pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,google-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboyannoyance-ads-digest256,fanboysocial-ads-digest256,easylist-ads-digest256,easyprivacy-ads-digest256,adguard-ads-digest256,social-tracking-protection-digest256,social-tracking-protection-facebook-digest256,social-tracking-protection-linkedin-digest256,social-tracking-protection-twitter-digest256");
 pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 // Set to a date in the past to force immediate download in new profiles.