]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Hide the tabs in full screen mode.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index 3d92e076e370a669d49b927146abe662f56c3d8e..4193364de5c0041abc3b31948a7272f19b0ef80c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
@@ -24,7 +24,6 @@ package com.stoutner.privacybrowser.activities;
 import android.Manifest;
 import android.annotation.SuppressLint;
 import android.app.Activity;
-import android.app.DialogFragment;
 import android.app.DownloadManager;
 import android.app.SearchManager;
 import android.content.ActivityNotFoundException;
@@ -37,7 +36,6 @@ import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -54,25 +52,6 @@ import android.os.Handler;
 import android.preference.PreferenceManager;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintManager;
-import android.support.annotation.NonNull;
-import android.support.design.widget.CoordinatorLayout;
-import android.support.design.widget.FloatingActionButton;
-import android.support.design.widget.NavigationView;
-import android.support.design.widget.Snackbar;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
-// `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
-import android.support.v4.content.pm.ShortcutInfoCompat;
-import android.support.v4.content.pm.ShortcutManagerCompat;
-import android.support.v4.graphics.drawable.IconCompat;
-import android.support.v4.view.GravityCompat;
-import android.support.v4.widget.DrawerLayout;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarDrawerToggle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.app.AppCompatDialogFragment;
-import android.support.v7.widget.Toolbar;
 import android.text.Editable;
 import android.text.Spanned;
 import android.text.TextWatcher;
@@ -92,9 +71,9 @@ import android.webkit.CookieManager;
 import android.webkit.HttpAuthHandler;
 import android.webkit.SslErrorHandler;
 import android.webkit.ValueCallback;
-import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
 import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
 import android.webkit.WebStorage;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
@@ -111,27 +90,50 @@ import android.widget.RadioButton;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.GravityCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.viewpager.widget.ViewPager;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.google.android.material.navigation.NavigationView;
+import com.google.android.material.snackbar.Snackbar;
+import com.google.android.material.tabs.TabLayout;
+
 import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
+import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
+import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
-import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
+import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
+import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
 import com.stoutner.privacybrowser.helpers.AdHelper;
 import com.stoutner.privacybrowser.helpers.BlockListHelper;
 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
+import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
-import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
-import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
+import com.stoutner.privacybrowser.views.NestedScrollWebView;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -150,125 +152,31 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+// TODO.  New tabs are white in dark mode.
+// TODO.  Find on page.
+
 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
-        CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
-        DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
-        HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener,
-        SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
-
-    // `darkTheme` is public static so it can be accessed from everywhere.
-    public static boolean darkTheme;
-
-    // `allowScreenshots` is public static so it can be accessed from everywhere.  It is also used in `onCreate()`.
-    public static boolean allowScreenshots;
-
-    // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
-    // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`.  It is also used in `onCreate()`,
-    // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
-    public static Bitmap favoriteIconBitmap;
-
-    // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
-    // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
-    public static String formattedUrlString;
-
-    // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`,
-    // and `ViewSslCertificateDialog`.  It is also used in `onCreate()`.
-    public static SslCertificate sslCertificate;
+        DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
+        EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener {
 
     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
     public static String orbotStatus;
 
-    // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`.  It is also used in `onCreate()`.
-    public static String webViewTitle;
-
-    // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`.  It is also used in `applyDomainSettings()`.
-    public static String appliedUserAgentString;
+    // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
+    public static WebViewPagerAdapter webViewPagerAdapter;
 
-    // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`
-    public static boolean reloadOnRestart;
-
-    // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`.  It is also used in `onRestart()`.
+    // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`.  They are used in `onRestart()`.
     public static boolean loadUrlOnRestart;
+    public static String urlToLoadOnRestart;
 
     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
     public static boolean restartFromBookmarksActivity;
 
-    // The block list versions are public static so they can be accessed from `AboutTabFragment`.  They are also used in `onCreate()`.
-    public static String easyListVersion;
-    public static String easyPrivacyVersion;
-    public static String fanboysAnnoyanceVersion;
-    public static String fanboysSocialVersion;
-    public static String ultraPrivacyVersion;
-
-    // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`.  They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
-    public static List<String[]> resourceRequests;
-    public static String[] whiteListResultStringArray;
-    private int blockedRequests;
-    private int easyListBlockedRequests;
-    private int easyPrivacyBlockedRequests;
-    private int fanboysAnnoyanceListBlockedRequests;
-    private int fanboysSocialBlockingListBlockedRequests;
-    private int ultraPrivacyBlockedRequests;
-    private int thirdPartyBlockedRequests;
-
-    public final static int REQUEST_DISPOSITION = 0;
-    public final static int REQUEST_URL = 1;
-    public final static int REQUEST_BLOCKLIST = 2;
-    public final static int REQUEST_SUBLIST = 3;
-    public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
-    public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
-
-    public final static int REQUEST_DEFAULT = 0;
-    public final static int REQUEST_ALLOWED = 1;
-    public final static int REQUEST_THIRD_PARTY = 2;
-    public final static int REQUEST_BLOCKED = 3;
-
-    public final static int MAIN_WHITELIST = 1;
-    public final static int FINAL_WHITELIST = 2;
-    public final static int DOMAIN_WHITELIST = 3;
-    public final static int DOMAIN_INITIAL_WHITELIST = 4;
-    public final static int DOMAIN_FINAL_WHITELIST = 5;
-    public final static int THIRD_PARTY_WHITELIST = 6;
-    public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
-    public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
-
-    public final static int MAIN_BLACKLIST = 9;
-    public final static int INITIAL_BLACKLIST = 10;
-    public final static int FINAL_BLACKLIST = 11;
-    public final static int DOMAIN_BLACKLIST = 12;
-    public final static int DOMAIN_INITIAL_BLACKLIST = 13;
-    public final static int DOMAIN_FINAL_BLACKLIST = 14;
-    public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
-    public final static int THIRD_PARTY_BLACKLIST = 16;
-    public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
-    public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
-    public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
-    public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
-    public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
-    public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
-
-    // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
-    // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
-    public static boolean blockAllThirdPartyRequests;
-
     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     public static String currentBookmarksFolder;
 
-    // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
-    public static int domainSettingsDatabaseId;
-
-    // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`.  They are also used in `onCreate()` and `applyDomainSettings()`.
-    public static String pinnedDomainSslIssuedToCNameString;
-    public static String pinnedDomainSslIssuedToONameString;
-    public static String pinnedDomainSslIssuedToUNameString;
-    public static String pinnedDomainSslIssuedByCNameString;
-    public static String pinnedDomainSslIssuedByONameString;
-    public static String pinnedDomainSslIssuedByUNameString;
-    public static Date pinnedDomainSslStartDate;
-    public static Date pinnedDomainSslEndDate;
-
     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
     public final static int UNRECOGNIZED_USER_AGENT = -1;
     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
@@ -278,115 +186,44 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
 
 
-    // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
-    private ActionBar appBar;
 
-    // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
-    private boolean navigatingHistory;
-
-    // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
-    private Bitmap favoriteIconDefaultBitmap;
-
-    // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
-    private DrawerLayout drawerLayout;
-
-    // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
-    private CoordinatorLayout rootCoordinatorLayout;
-
-    // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
-    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
-    private WebView mainWebView;
-
-    // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
-    private FrameLayout fullScreenVideoFrameLayout;
-
-    // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
-    private SwipeRefreshLayout swipeRefreshLayout;
-
-    // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
-    private RelativeLayout urlAppBarRelativeLayout;
-
-    // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
-    private ImageView favoriteIconImageView;
-
-    // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
-    private CookieManager cookieManager;
+    // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
+    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`.
+    private NestedScrollWebView currentWebView;
 
     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
     private final Map<String, String> customHeaders = new HashMap<>();
 
-    // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
-    private boolean javaScriptEnabled;
-
-    // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
-    private boolean firstPartyCookiesEnabled;
-
-    // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
-    private boolean thirdPartyCookiesEnabled;
-
-    // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
-    private boolean domStorageEnabled;
-
-    // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.  It can be removed once the minimum API >= 26.
-    private boolean saveFormDataEnabled;
-
-    // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and  `applyDomainSettings()`.
-    private boolean nightMode;
-
-    // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
-    private String homepage;
-
-    // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
+    // The search URL is set in `applyProxyThroughOrbot()` and used in `onCreate()`, `onNewIntent()`, `loadURLFromTextBox()`, and `initializeWebView()`.
     private String searchURL;
 
-    // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
-    private Menu mainMenu;
+    // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
+    private Menu optionsMenu;
 
-    // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
-    private MenuItem refreshMenuItem;
-
-    // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
-    private MenuItem blocklistsMenuItem;
-    private MenuItem easyListMenuItem;
-    private MenuItem easyPrivacyMenuItem;
-    private MenuItem fanboysAnnoyanceListMenuItem;
-    private MenuItem fanboysSocialBlockingListMenuItem;
-    private MenuItem ultraPrivacyMenuItem;
-    private MenuItem blockAllThirdPartyRequestsMenuItem;
-
-    // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
-    private boolean easyListEnabled;
-    private boolean easyPrivacyEnabled;
-    private boolean fanboysAnnoyanceListEnabled;
-    private boolean fanboysSocialBlockingListEnabled;
-    private boolean ultraPrivacyEnabled;
+    // The blocklists are populated in `onCreate()` and accessed from `initializeWebView()`.
+    private ArrayList<List<String[]>> easyList;
+    private ArrayList<List<String[]>> easyPrivacy;
+    private ArrayList<List<String[]>> fanboysAnnoyanceList;
+    private ArrayList<List<String[]>> fanboysSocialList;
+    private ArrayList<List<String[]>> ultraPrivacy;
 
     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
     private String webViewDefaultUserAgent;
 
-    // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
-    private String defaultCustomUserAgentString;
-
-    // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
-    private Runtime privacyBrowserRuntime;
-
     // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
     private boolean proxyThroughOrbot;
 
-    // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
+    // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
     private boolean incognitoModeEnabled;
 
-    // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
+    // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
     private boolean fullScreenBrowsingModeEnabled;
 
     // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
     private boolean inFullScreenBrowsingMode;
 
-    // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
-    private boolean hideSystemBarsOnFullscreen;
-
-    // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
-    private boolean translucentNavigationBarOnFullscreen;
+    // The hide app bar tracker is used in `applyAppSettings()` and `initializeWebView()`.
+    private boolean hideAppBar;
 
     // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
     private boolean reapplyDomainSettingsOnRestart;
@@ -397,84 +234,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
     private boolean displayingFullScreenVideo;
 
-    // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
-    private boolean downloadWithExternalApp;
-
-    // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
-    private String currentDomainName;
-
-    // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
-    private boolean ignorePinnedSslCertificate;
-
     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
     private BroadcastReceiver orbotStatusBroadcastReceiver;
 
     // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
     private boolean waitingForOrbot;
 
-    // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
-    private boolean domainSettingsApplied;
-
-    // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
-    private Boolean domainSettingsJavaScriptEnabled;
-
-    // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
-    private String waitingForOrbotHtmlString;
-
-    // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
-    private String privateDataDirectoryString;
-
-    // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
-    private LinearLayout findOnPageLinearLayout;
-
-    // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
-    private EditText findOnPageEditText;
-
-    // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
-    private boolean displayAdditionalAppBarIcons;
-
-    // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
-    private ActionBarDrawerToggle drawerToggle;
-
-    // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
-    private Toolbar supportAppBar;
-
-    // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
-    private EditText urlTextBox;
+    // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
+    private ActionBarDrawerToggle actionBarDrawerToggle;
 
     // The color spans are used in `onCreate()` and `highlightUrlText()`.
     private ForegroundColorSpan redColorSpan;
     private ForegroundColorSpan initialGrayColorSpan;
     private ForegroundColorSpan finalGrayColorSpan;
 
-    // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
-    private SslErrorHandler sslErrorHandler;
-
-    // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
-    private static HttpAuthHandler httpAuthHandler;
-
-    // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
-    private InputMethodManager inputMethodManager;
-
-    // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
-    private RelativeLayout mainWebViewRelativeLayout;
-
-    // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
-    private boolean urlIsLoading;
-
-    // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
-    private boolean pinnedDomainSslCertificate;
+    // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
+    private int drawerHeaderPaddingLeftAndRight;
+    private int drawerHeaderPaddingTop;
+    private int drawerHeaderPaddingBottom;
 
     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
     // and `loadBookmarksFolder()`.
     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
 
-    // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
-    private ListView bookmarksListView;
-
-    // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
-    private TextView bookmarksTitleTextView;
-
     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     private Cursor bookmarksCursor;
 
@@ -487,7 +269,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
     private ValueCallback<Uri[]> fileChooserCallback;
 
-    // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
+    // The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
     private String downloadUrl;
     private String downloadContentDisposition;
     private long downloadContentLength;
@@ -495,27 +277,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
     private String downloadImageUrl;
 
-    // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
-    private ArrayAdapter<CharSequence> userAgentNamesArray;
-    private String[] userAgentDataArray;
-
-    // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
+    // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
     private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
 
     @Override
-    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.  The whole premise of Privacy Browser is built around an understanding of these dangers.
-    // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
-    @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
-    // Remove Android Studio's warning about deprecations.  The deprecated `getColor()` must be used until API >= 23.
-    @SuppressWarnings("deprecation")
+    // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
+    @SuppressLint("ClickableViewAccessibility")
     protected void onCreate(Bundle savedInstanceState) {
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
         // Get the theme and screenshot preferences.
-        darkTheme = sharedPreferences.getBoolean("dark_theme", false);
-        allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
+        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
 
         // Disable screenshots if not allowed.
         if (!allowScreenshots) {
@@ -533,44 +308,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         super.onCreate(savedInstanceState);
 
         // Set the content view.
-        setContentView(R.layout.main_drawerlayout);
+        setContentView(R.layout.main_framelayout);
+
+        // Get a handle for the input method.
+        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // Get a handle for the resources.
-        Resources resources = getResources();
+        // Remove the lint warning below that the input method manager might be null.
+        assert inputMethodManager != null;
 
-        // Get a handle for `inputMethodManager`.
-        inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+        // Get a handle for the toolbar.
+        Toolbar toolbar = findViewById(R.id.toolbar);
 
-        // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
-        supportAppBar = findViewById(R.id.app_bar);
-        setSupportActionBar(supportAppBar);
-        appBar = getSupportActionBar();
+        // Set the action bar.  `SupportActionBar` must be used until the minimum API is >= 21.
+        setSupportActionBar(toolbar);
 
-        // This is needed to get rid of the Android Studio warning that `appBar` might be null.
-        assert appBar != null;
+        // Get a handle for the action bar.
+        ActionBar actionBar = getSupportActionBar();
 
-        // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
-        appBar.setCustomView(R.layout.url_app_bar);
-        appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+        // This is needed to get rid of the Android Studio warning that the action bar might be null.
+        assert actionBar != null;
+
+        // Add the custom layout, which shows the URL text bar.
+        actionBar.setCustomView(R.layout.url_app_bar);
+        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
         // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
-        redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
-        initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
-        finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
+        redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+        finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
 
-        // Get a handle for `urlTextBox`.
-        urlTextBox = findViewById(R.id.url_edittext);
+        // Get handles for the URL views.
+        EditText urlEditText = findViewById(R.id.url_edittext);
 
         // Remove the formatting from `urlTextBar` when the user is editing the text.
-        urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
+        urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
             if (hasFocus) {  // The user is editing the URL text box.
                 // Remove the highlighting.
-                urlTextBox.getText().removeSpan(redColorSpan);
-                urlTextBox.getText().removeSpan(initialGrayColorSpan);
-                urlTextBox.getText().removeSpan(finalGrayColorSpan);
+                urlEditText.getText().removeSpan(redColorSpan);
+                urlEditText.getText().removeSpan(initialGrayColorSpan);
+                urlEditText.getText().removeSpan(finalGrayColorSpan);
             } else {  // The user has stopped editing the URL text box.
                 // Move to the beginning of the string.
-                urlTextBox.setSelection(0);
+                urlEditText.setSelection(0);
 
                 // Reapply the highlighting.
                 highlightUrlText();
@@ -578,7 +357,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Set the go button on the keyboard to load the URL in `urlTextBox`.
-        urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
+        urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
             // If the event is a key-down event on the `enter` button, load the URL.
             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                 // Load the URL into the mainWebView and consume the event.
@@ -592,11 +371,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
-        // Set `waitingForOrbotHTMLString`.
-        waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
-
-        // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
-        currentDomainName = "";
+        // Initialize the Orbot status and the waiting for Orbot trackers.
         orbotStatus = "unknown";
         waitingForOrbot = false;
 
@@ -609,11 +384,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
                 if (orbotStatus.equals("ON") && waitingForOrbot) {
-                    // Reset `waitingForOrbot`.
+                    // Reset the waiting for Orbot status.
                     waitingForOrbot = false;
 
-                    // Load `formattedUrlString
-                    loadUrl(formattedUrlString);
+                    // Get the intent that started the app.
+                    Intent launchingIntent = getIntent();
+
+                    // Get the information from the intent.
+                    String launchingIntentAction = launchingIntent.getAction();
+                    Uri launchingIntentUriData = launchingIntent.getData();
+
+                    // If the intent action is a web search, perform the search.
+                    if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+                        // Create an encoded URL string.
+                        String encodedUrlString;
+
+                        // Sanitize the search input and convert it to a search.
+                        try {
+                            encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
+                        } catch (UnsupportedEncodingException exception) {
+                            encodedUrlString = "";
+                        }
+
+                        // Load the completed search URL.
+                        loadUrl(searchURL + encodedUrlString);
+                    } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
+                        // Load the URL from the intent.
+                        loadUrl(launchingIntentUriData.toString());
+                    } else {  // The is no URL in the intent.
+                        // Select the homepage based on the proxy through Orbot status.
+                        if (proxyThroughOrbot) {
+                            // Load the Tor homepage.
+                            loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
+                        } else {
+                            // Load the normal homepage.
+                            loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
+                        }
+                    }
                 }
             }
         };
@@ -621,167 +428,171 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Register `orbotStatusBroadcastReceiver` on `this` context.
         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
 
-        // Get handles for views that need to be accessed.
-        drawerLayout = findViewById(R.id.drawerlayout);
-        rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
-        bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
-        bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
+        // Instantiate the blocklist helper.
+        BlockListHelper blockListHelper = new BlockListHelper();
+
+        // Parse the block lists.
+        easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
+        easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
+        fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
+        fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
+        ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
+
+        // Get handles for views that need to be modified.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        NavigationView navigationView = findViewById(R.id.navigationview);
+        TabLayout tabLayout = findViewById(R.id.tablayout);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+        ViewPager webViewPager = findViewById(R.id.webviewpager);
+        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
-        mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
-        mainWebView = findViewById(R.id.main_webview);
-        findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
-        findOnPageEditText = findViewById(R.id.find_on_page_edittext);
-        fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
-        urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
-        favoriteIconImageView = findViewById(R.id.favorite_icon);
+        EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
 
-        // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
-        if (darkTheme) {
-            launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
-            createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
-            createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
-            bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
-        } else {
-            launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
-            createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
-            createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
-            bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
-        }
+        // Listen for touches on the navigation menu.
+        navigationView.setNavigationItemSelectedListener(this);
 
-        // Set the launch bookmarks activity FAB to launch the bookmarks activity.
-        launchBookmarksActivityFab.setOnClickListener(v -> {
-            // Create an intent to launch the bookmarks activity.
-            Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
+        // Get handles for the navigation menu and the back and forward menu items.  The menu is zero-based.
+        Menu navigationMenu = navigationView.getMenu();
+        MenuItem navigationCloseTabMenuItem = navigationMenu.getItem(0);
+        MenuItem navigationBackMenuItem = navigationMenu.getItem(3);
+        MenuItem navigationForwardMenuItem = navigationMenu.getItem(4);
+        MenuItem navigationHistoryMenuItem = navigationMenu.getItem(5);
+        MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
 
-            // Include the current folder with the `Intent`.
-            bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
+        // Initialize the web view pager adapter.
+        webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
 
-            // Make it so.
-            startActivity(bookmarksIntent);
-        });
+        // Set the pager adapter on the web view pager.
+        webViewPager.setAdapter(webViewPagerAdapter);
 
-        // Set the create new bookmark folder FAB to display an alert dialog.
-        createBookmarkFolderFab.setOnClickListener(v -> {
-            // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
-            AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
-            createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder));
-        });
+        // Store up to 100 tabs in memory.
+        webViewPager.setOffscreenPageLimit(100);
 
-        // Set the create new bookmark FAB to display an alert dialog.
-        createBookmarkFab.setOnClickListener(view -> {
-            // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
-            AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
-            createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark));
-        });
+        // Update the web view pager every time a tab is modified.
+        webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+                // Do nothing.
+            }
 
-        // Create a double-tap listener to toggle full-screen mode.
-        final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
-            // Override `onDoubleTap()`.  All other events are handled using the default settings.
             @Override
-            public boolean onDoubleTap(MotionEvent event) {
-                if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
-                    // Toggle `inFullScreenBrowsingMode`.
-                    inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
+            public void onPageSelected(int position) {
+                // Set the current WebView.
+                setCurrentWebView(position);
 
-                    if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
-                        // Hide the `appBar`.
-                        appBar.hide();
+                // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled via swiping in the view pager or by creating a new tab.
+                if (tabLayout.getSelectedTabPosition() != position) {
+                    // Create a handler to select the tab.
+                    Handler selectTabHandler = new Handler();
 
-                        // Hide the banner ad in the free flavor.
-                        if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                            AdHelper.hideAd(findViewById(R.id.adview));
-                        }
+                    // Create a runnable select the new tab.
+                    Runnable selectTabRunnable = () -> {
+                        // Get a handle for the tab.
+                        TabLayout.Tab tab = tabLayout.getTabAt(position);
 
-                        // Modify the system bars.
-                        if (hideSystemBarsOnFullscreen) {  // Hide everything.
-                            // Remove the translucent overlays.
-                            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
-                            // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
-                            drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-
-                            /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                             */
-                            rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
-                            // Set `rootCoordinatorLayout` to fill the whole screen.
-                            rootCoordinatorLayout.setFitsSystemWindows(false);
-                        } else {  // Hide everything except the status and navigation bars.
-                            // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
-                            rootCoordinatorLayout.setFitsSystemWindows(false);
-
-                            // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
-                            if (translucentNavigationBarOnFullscreen) {
-                                // Set the navigation bar to be translucent.
-                                getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-                            }
-                        }
-                    } else {  // Switch to normal viewing mode.
-                        // Show the `appBar`.
-                        appBar.show();
+                        // Assert that the tab is not null.
+                        assert tab != null;
 
-                        // Show the `BannerAd` in the free flavor.
-                        if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
-                        }
+                        // Select the tab.
+                        tab.select();
+                    };
 
-                        // Remove the translucent navigation bar flag if it is set.
-                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+                    // Select the tab layout after 100 milliseconds, which leaves enough time for a new tab to be created.
+                    selectTabHandler.postDelayed(selectTabRunnable, 100);
+                }
+            }
 
-                        // Add the translucent status flag if it is unset.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
-                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            @Override
+            public void onPageScrollStateChanged(int state) {
+                // Do nothing.
+            }
+        });
 
-                        // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
-                        rootCoordinatorLayout.setSystemUiVisibility(0);
+        // Display the View SSL Certificate dialog when the currently selected tab is reselected.
+        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+            @Override
+            public void onTabSelected(TabLayout.Tab tab) {
+                // Select the same page in the view pager.
+                webViewPager.setCurrentItem(tab.getPosition());
+            }
 
-                        // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
-                        rootCoordinatorLayout.setFitsSystemWindows(true);
-                    }
+            @Override
+            public void onTabUnselected(TabLayout.Tab tab) {
+                // Do nothing.
+            }
 
-                    // Consume the double-tap.
-                    return true;
-                } else { // Do not consume the double-tap because full screen browsing mode is disabled.
-                    return false;
-                }
+            @Override
+            public void onTabReselected(TabLayout.Tab tab) {
+                // Instantiate the View SSL Certificate dialog.
+                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
+
+                // Display the View SSL Certificate dialog.
+                viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
             }
         });
 
-        // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
-        mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
-            // Call `performClick()` on the view, which is required for accessibility.
-            v.performClick();
+        // Add the first tab.
+        addTab(null);
+
+        // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
+        // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
+        if (darkTheme) {
+            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
+            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
+            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
+            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
+        } else {
+            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
+            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
+            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
+            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
+        }
+
+        // Set the launch bookmarks activity FAB to launch the bookmarks activity.
+        launchBookmarksActivityFab.setOnClickListener(v -> {
+            // Get a copy of the favorite icon bitmap.
+            Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
+
+            // Create a favorite icon byte array output stream.
+            ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+
+            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+
+            // Convert the favorite icon byte array stream to a byte array.
+            byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+
+            // Create an intent to launch the bookmarks activity.
+            Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
+
+            // Add the extra information to the intent.
+            bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
+            bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
+            bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
+            bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
 
-            // Send the `event` to `gestureDetector`.
-            return gestureDetector.onTouchEvent(event);
+            // Make it so.
+            startActivity(bookmarksIntent);
         });
 
-        // Update `findOnPageCountTextView`.
-        mainWebView.setFindListener(new WebView.FindListener() {
-            // Get a handle for `findOnPageCountTextView`.
-            final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
+        // Set the create new bookmark folder FAB to display an alert dialog.
+        createBookmarkFolderFab.setOnClickListener(v -> {
+            // Create a create bookmark folder dialog.
+            DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
 
-            @Override
-            public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
-                if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
-                    // Set `findOnPageCountTextView` to `0/0`.
-                    findOnPageCountTextView.setText(R.string.zero_of_zero);
-                } else if (isDoneCounting) {  // There are matches.
-                    // `activeMatchOrdinal` is zero-based.
-                    int activeMatch = activeMatchOrdinal + 1;
+            // Show the create bookmark folder dialog.
+            createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
+        });
 
-                    // Build the match string.
-                    String matchString = activeMatch + "/" + numberOfMatches;
+        // Set the create new bookmark FAB to display an alert dialog.
+        createBookmarkFab.setOnClickListener(view -> {
+            // Instantiate the create bookmark dialog.
+            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
 
-                    // Set `findOnPageCountTextView`.
-                    findOnPageCountTextView.setText(matchString);
-                }
-            }
+            // Display the create bookmark dialog.
+            createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
         });
 
         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
@@ -799,15 +610,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             @Override
             public void afterTextChanged(Editable s) {
                 // Search for the text in `mainWebView`.
-                mainWebView.findAllAsync(findOnPageEditText.getText().toString());
+                currentWebView.findAllAsync(findOnPageEditText.getText().toString());
             }
         });
 
         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
         findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
-                // Hide the soft keyboard.  `0` indicates no additional flags.
-                inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+                // Hide the soft keyboard.
+                inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
 
                 // Consume the event.
                 return true;
@@ -818,34 +629,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Implement swipe to refresh.
-        swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-        swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
+        swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
+
+        // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+        swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayout.getProgressViewStartOffset() - 10, swipeRefreshLayout.getProgressViewEndOffset());
 
         // Set the swipe to refresh color according to the theme.
         if (darkTheme) {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
+            swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
             swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
         } else {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
+            swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
         }
 
         // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
 
-        // Listen for touches on the navigation menu.
-        final NavigationView navigationView = findViewById(R.id.navigationview);
-        navigationView.setNavigationItemSelectedListener(this);
-
-        // Get handles for `navigationMenu` and the back and forward menu items.  The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu.
-        final Menu navigationMenu = navigationView.getMenu();
-        final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
-        final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
-        final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
-        final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
-
-        // Initialize the bookmarks database helper.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
-        // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+        // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
 
         // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
@@ -859,7 +660,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             int databaseID = (int) id;
 
             // Get the bookmark cursor for this ID and move it to the first row.
-            Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
+            Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
             bookmarkCursor.moveToFirst();
 
             // Act upon the bookmark according to the type.
@@ -893,12 +694,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
 
                 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
-                AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
-                editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder));
+                DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
+                editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
             } else {
                 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
-                AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
-                editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark));
+                DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
+                editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark));
             }
 
             // Consume the event.
@@ -906,18 +707,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Get the status bar pixel size.
-        int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
-        int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
+        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
 
         // Get the resource density.
-        float screenDensity = resources.getDisplayMetrics().density;
+        float screenDensity = getResources().getDisplayMetrics().density;
 
         // Calculate the drawer header padding.  This is used to move the text in the drawer headers below any cutouts.
-        int drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
-        int drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
-        int drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
 
-        // The drawer listener is used to update the navigation menu.
+        // The drawer listener is used to update the navigation menu.`
         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
             @Override
             public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
@@ -938,3809 +739,4626 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     TextView navigationHeaderTextView = findViewById(R.id.navigationText);
                     TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
 
-                    // Apply the calculated drawer paddings.  This moves the text in the header below any cutouts.
-                    navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
-                    bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+                    // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
+                    if (navigationHeaderTextView != null) {
+                        navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+                    }
+
+                    // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
+                    if (bookmarksHeaderTextView != null) {
+                        bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+                    }
 
-                    // Update the back, forward, history, and requests menu items.
-                    navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
-                    navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
-                    navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
-                    navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                    // Update the navigation menu items.
+                    navigationCloseTabMenuItem.setEnabled(tabLayout.getTabCount() > 1);
+                    navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
+                    navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
+                    navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
+                    navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
 
                     // Hide the keyboard (if displayed).
-                    inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+                    inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
 
                     // Clear the focus from from the URL text box and the WebView.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
-                    urlTextBox.clearFocus();
-                    mainWebView.clearFocus();
+                    urlEditText.clearFocus();
+                    currentWebView.clearFocus();
                 }
             }
         });
 
-        // drawerToggle creates the hamburger icon at the start of the AppBar.
-        drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
+        // Create the hamburger icon at the start of the AppBar.
+        actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
 
-        // Get a handle for the progress bar.
-        final ProgressBar progressBar = findViewById(R.id.progress_bar);
+        // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
+        customHeaders.put("X-Requested-With", "");
 
-        mainWebView.setWebChromeClient(new WebChromeClient() {
-            // Update the progress bar when a page is loading.
-            @Override
-            public void onProgressChanged(WebView view, int progress) {
-                // Inject the night mode CSS if night mode is enabled.
-                if (nightMode) {
-                    // `background-color: #212121` sets the background to be dark gray.  `color: #BDBDBD` sets the text color to be light gray.  `box-shadow: none` removes a lower underline on links
-                    // used by WordPress.  `text-decoration: none` removes all text underlines.  `text-shadow: none` removes text shadows, which usually have a hard coded color.
-                    // `border: none` removes all borders, which can also be used to underline text.
-                    // `a {color: #1565C0}` sets links to be a dark blue.  `!important` takes precedent over any existing sub-settings.
-                    mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
-                            "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
-                            "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> {
-                                // Initialize a handler to display `mainWebView`.
-                                Handler displayWebViewHandler = new Handler();
-
-                                // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
-                                Runnable displayWebViewRunnable = () -> {
-                                    // Only display `mainWebView` if the progress bar is one.  This prevents the display of the `WebView` while it is still loading.
-                                    if (progressBar.getVisibility() == View.GONE) {
-                                        mainWebView.setVisibility(View.VISIBLE);
-                                    }
-                                };
+        // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
+        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
 
-                                // Displaying of `mainWebView` after 500 milliseconds.
-                                displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
-                            });
-                }
+        // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
+        @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
 
-                // Update the progress bar.
-                progressBar.setProgress(progress);
+        // Get a handle for the WebView.
+        WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
 
-                // Set the visibility of the progress bar.
-                if (progress < 100) {
-                    // Show the progress bar.
-                    progressBar.setVisibility(View.VISIBLE);
-                } else {
-                    // Hide the progress bar.
-                    progressBar.setVisibility(View.GONE);
+        // Store the default user agent.
+        webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
 
-                    // Display `mainWebView` if night mode is disabled.
-                    // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not
-                    // currently enabled.
-                    if (!nightMode) {
-                        mainWebView.setVisibility(View.VISIBLE);
-                    }
+        // Destroy the bare WebView.
+        bareWebView.destroy();
+    }
 
-                    //Stop the swipe to refresh indicator if it is running
-                    swipeRefreshLayout.setRefreshing(false);
-                }
-            }
+    @Override
+    protected void onNewIntent(Intent intent) {
+        // Get the information from the intent.
+        String intentAction = intent.getAction();
+        Uri intentUriData = intent.getData();
 
-            // Set the favorite icon when it changes.
-            @Override
-            public void onReceivedIcon(WebView view, Bitmap icon) {
-                // Only update the favorite icon if the website has finished loading.
-                if (progressBar.getVisibility() == View.GONE) {
-                    // Save a copy of the favorite icon.
-                    favoriteIconBitmap = icon;
+        // Only process the URI if it contains data.  If the user pressed the desktop icon after the app was already running the URI will be null.
+        if (intentUriData != null) {
+            // Sets the new intent as the activity intent, which replaces the one that originally started the app.
+            setIntent(intent);
 
-                    // Place the favorite icon in the appBar.
-                    favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
-                }
-            }
+            // Add a new tab.
+            addTab(null);
 
-            // Save a copy of the title when it changes.
-            @Override
-            public void onReceivedTitle(WebView view, String title) {
-                // Save a copy of the title.
-                webViewTitle = title;
-            }
+            // Create a URL string.
+            String url;
 
-            // Enter full screen video.
-            @Override
-            public void onShowCustomView(View view, CustomViewCallback callback) {
-                // Set the full screen video flag.
-                displayingFullScreenVideo = true;
+            // If the intent action is a web search, perform the search.
+            if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+                // Create an encoded URL string.
+                String encodedUrlString;
 
-                // Pause the ad if this is the free flavor.
-                if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                    AdHelper.pauseAd(findViewById(R.id.adview));
+                // Sanitize the search input and convert it to a search.
+                try {
+                    encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+                } catch (UnsupportedEncodingException exception) {
+                    encodedUrlString = "";
                 }
 
-                // Remove the translucent overlays.
-                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
-                // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
-                drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+                // Add the base search URL.
+                url = searchURL + encodedUrlString;
+            } else {  // The intent should contain a URL.
+                // Set the intent data as the URL.
+                url = intentUriData.toString();
+            }
 
-                /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                 */
-                rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+            // Load the URL.
+            loadUrl(url);
 
-                // Set `rootCoordinatorLayout` to fill the entire screen.
-                rootCoordinatorLayout.setFitsSystemWindows(false);
+            // Get a handle for the drawer layout.
+            DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-                // Disable the sliding drawers.
-                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+            // Close the navigation drawer if it is open.
+            if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
+                drawerLayout.closeDrawer(GravityCompat.START);
+            }
 
-                // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
-                fullScreenVideoFrameLayout.addView(view);
-                fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
+            // Close the bookmarks drawer if it is open.
+            if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
+                drawerLayout.closeDrawer(GravityCompat.END);
             }
 
-            // Exit full screen video.
-            @Override
-            public void onHideCustomView() {
-                // Unset the full screen video flag.
-                displayingFullScreenVideo = false;
+            // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
+            currentWebView.requestFocus();
+        }
+    }
 
-                // Hide `fullScreenVideoFrameLayout`.
-                fullScreenVideoFrameLayout.removeAllViews();
-                fullScreenVideoFrameLayout.setVisibility(View.GONE);
+    @Override
+    public void onRestart() {
+        // Run the default commands.
+        super.onRestart();
 
-                // Enable the sliding drawers.
-                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
-                // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
-                if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-                    if (hideSystemBarsOnFullscreen) {  // Hide everything.
-                        // Remove the translucent navigation setting if it is currently flagged.
-                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-
-                        // Remove the translucent status bar overlay.
-                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
+        if (proxyThroughOrbot) {
+            // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
+            Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
 
-                        // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
-                        drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+            // Send the intent to the Orbot package.
+            orbotIntent.setPackage("org.torproject.android");
 
-                        /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                         * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                         * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                         */
-                        rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-                    } else {  // Hide everything except the status and navigation bars.
-                        // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
-                        rootCoordinatorLayout.setSystemUiVisibility(0);
+            // Make it so.
+            sendBroadcast(orbotIntent);
+        }
 
-                        // Add the translucent status flag if it is unset.
-                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        // Apply the app settings if returning from the Settings activity.
+        if (reapplyAppSettingsOnRestart) {
+            // Reset the reapply app settings on restart tracker.
+            reapplyAppSettingsOnRestart = false;
 
-                        if (translucentNavigationBarOnFullscreen) {
-                            // Set the navigation bar to be translucent.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
-                            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-                        } else {
-                            // Set the navigation bar to be black.
-                            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-                        }
-                    }
-                } else {  // Switch to normal viewing mode.
-                    // Show the `appBar` if `findOnPageLinearLayout` is not visible.
-                    if (findOnPageLinearLayout.getVisibility() == View.GONE) {
-                        appBar.show();
-                    }
+            // Apply the app settings.
+            applyAppSettings();
+        }
 
-                    // Show the `BannerAd` in the free flavor.
-                    if (BuildConfig.FLAVOR.contentEquals("free")) {
-                        // Initialize the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                        AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
-                    }
+        // Apply the domain settings if returning from the settings or domains activity.
+        if (reapplyDomainSettingsOnRestart) {
+            // Reset the reapply domain settings on restart tracker.
+            reapplyDomainSettingsOnRestart = false;
 
-                    // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
-                    rootCoordinatorLayout.setSystemUiVisibility(0);
+            // Reapply the domain settings for each tab.
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-                    // Remove the translucent navigation bar flag if it is set.
-                    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-                    // Add the translucent status flag if it is unset.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
-                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                // Only reload the WebViews if they exist.
+                if (fragmentView != null) {
+                    // Get the nested scroll WebView from the tab fragment.
+                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                    // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
-                    rootCoordinatorLayout.setFitsSystemWindows(true);
-                }
+                    // Reset the current domain name so the domain settings will be reapplied.
+                    nestedScrollWebView.resetCurrentDomainName();
 
-                // Show the ad if this is the free flavor.
-                if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+                    // Reapply the domain settings.
+                    applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
                 }
             }
+        }
 
-            // Upload files.
-            @Override
-            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
-                // Show the file chooser if the device is running API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Store the file path callback.
-                    fileChooserCallback = filePathCallback;
+        // Load the URL on restart (used when loading a bookmark).
+        if (loadUrlOnRestart) {
+            // Load the specified URL.
+            loadUrl(urlToLoadOnRestart);
 
-                    // Create an intent to open a chooser based ont the file chooser parameters.
-                    Intent fileChooserIntent = fileChooserParams.createIntent();
+            // Reset the load on restart tracker.
+            loadUrlOnRestart = false;
+        }
 
-                    // Open the file chooser.  Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
-                    startActivityForResult(fileChooserIntent, 0);
-                }
-                return true;
-            }
-        });
+        // Update the bookmarks drawer if returning from the Bookmarks activity.
+        if (restartFromBookmarksActivity) {
+            // Get a handle for the drawer layout.
+            DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-        // Register `mainWebView` for a context menu.  This is used to see link targets and download images.
-        registerForContextMenu(mainWebView);
+            // Close the bookmarks drawer.
+            drawerLayout.closeDrawer(GravityCompat.END);
 
-        // Allow the downloading of files.
-        mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
-            // Check if the download should be processed by an external app.
-            if (downloadWithExternalApp) {  // Download with an external app.
-                openUrlWithExternalApp(url);
-            } else {  // Download with Android's download manager.
-                // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
-                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission has not been granted.
-                    // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
+            // Reload the bookmarks drawer.
+            loadBookmarksFolder();
 
-                    // Store the variables for future use by `onRequestPermissionsResult()`.
-                    downloadUrl = url;
-                    downloadContentDisposition = contentDisposition;
-                    downloadContentLength = contentLength;
+            // Reset `restartFromBookmarksActivity`.
+            restartFromBookmarksActivity = false;
+        }
 
-                    // Show a dialog if the user has previously denied the permission.
-                    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                        // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                        DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+        // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
+        updatePrivacyIcons(true);
+    }
 
-                        // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                        downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                    } else {  // Show the permission request directly.
-                        // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
-                        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                    }
-                } else {  // The storage permission has already been granted.
-                    // Get a handle for the download file alert dialog.
-                    AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
+    @Override
+    public void onResume() {
+        // Run the default commands.
+        super.onResume();
 
-                    // Show the download file alert dialog.
-                    downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
-                }
-            }
-        });
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-        // Allow pinch to zoom.
-        mainWebView.getSettings().setBuiltInZoomControls(true);
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
 
-        // Hide zoom controls.
-        mainWebView.getSettings().setDisplayZoomControls(false);
+            // Only resume the WebViews if they exist (they won't when the app is first created).
+            if (fragmentView != null) {
+                // Get the nested scroll WebView from the tab fragment.
+                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-        // Set `mainWebView` to use a wide viewport.  Otherwise, some web pages will be scrunched and some content will render outside the screen.
-        mainWebView.getSettings().setUseWideViewPort(true);
+                // Resume the nested scroll WebView JavaScript timers.
+                nestedScrollWebView.resumeTimers();
 
-        // Set `mainWebView` to load in overview mode (zoomed out to the maximum width).
-        mainWebView.getSettings().setLoadWithOverviewMode(true);
+                // Resume the nested scroll WebView.
+                nestedScrollWebView.onResume();
+            }
+        }
 
-        // Explicitly disable geolocation.
-        mainWebView.getSettings().setGeolocationEnabled(false);
+        // Display a message to the user if waiting for Orbot.
+        if (waitingForOrbot && !orbotStatus.equals("ON")) {
+            // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+            currentWebView.getSettings().setUseWideViewPort(false);
 
-        // Initialize cookieManager.
-        cookieManager = CookieManager.getInstance();
+            // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
+            currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
+        }
 
-        // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
-        customHeaders.put("X-Requested-With", "");
+        if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
+            // Get a handle for the root frame layouts.
+            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
 
-        // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
-        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-        // Get a handle for the `Runtime`.
-        privacyBrowserRuntime = Runtime.getRuntime();
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // Resume the adView for the free flavor.
+            // Resume the ad.
+            AdHelper.resumeAd(findViewById(R.id.adview));
+        }
+    }
 
-        // Store the application's private data directory.
-        privateDataDirectoryString = getApplicationInfo().dataDir;
-        // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+    @Override
+    public void onPause() {
+        // Run the default commands.
+        super.onPause();
 
-        // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
-        inFullScreenBrowsingMode = false;
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-        // Initialize the privacy settings variables.
-        javaScriptEnabled = false;
-        firstPartyCookiesEnabled = false;
-        thirdPartyCookiesEnabled = false;
-        domStorageEnabled = false;
-        saveFormDataEnabled = false;  // Form data can be removed once the minimum API >= 26.
-        nightMode = false;
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
 
-        // Store the default user agent.
-        webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
+            // Only pause the WebViews if they exist (they won't when the app is first created).
+            if (fragmentView != null) {
+                // Get the nested scroll WebView from the tab fragment.
+                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-        // Initialize the WebView title.
-        webViewTitle = getString(R.string.no_title);
+                // Pause the nested scroll WebView.
+                nestedScrollWebView.onPause();
 
-        // Initialize the favorite icon bitmap.  `ContextCompat` must be used until API >= 21.
-        Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
-        BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
-        assert favoriteIconBitmapDrawable != null;
-        favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
+                // Pause the nested scroll WebView JavaScript timers.
+                nestedScrollWebView.pauseTimers();
+            }
+        }
 
-        // If the favorite icon is null, load the default.
-        if (favoriteIconBitmap == null) {
-            favoriteIconBitmap = favoriteIconDefaultBitmap;
+        // Pause the ad or it will continue to consume resources in the background on the free flavor.
+        if (BuildConfig.FLAVOR.contentEquals("free")) {
+            // Pause the ad.
+            AdHelper.pauseAd(findViewById(R.id.adview));
         }
+    }
 
-        // Initialize the user agent array adapter and string array.
-        userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
-        userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
+    @Override
+    public void onDestroy() {
+        // Unregister the Orbot status broadcast receiver.
+        this.unregisterReceiver(orbotStatusBroadcastReceiver);
 
-        // Apply the app settings from the shared preferences.
-        applyAppSettings();
+        // Close the bookmarks cursor and database.
+        bookmarksCursor.close();
+        bookmarksDatabaseHelper.close();
 
-        // Instantiate the block list helper.
-        BlockListHelper blockListHelper = new BlockListHelper();
+        // Run the default commands.
+        super.onDestroy();
+    }
 
-        // Initialize the list of resource requests.
-        resourceRequests = new ArrayList<>();
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu.  This adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.webview_options_menu, menu);
 
-        // Parse the block lists.
-        final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
-        final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
-        final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
-        final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
-        final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
-
-        // Store the list versions.
-        easyListVersion = easyList.get(0).get(0)[0];
-        easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
-        fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
-        fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
-        ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
-
-        // Get a handle for the activity.  This is used to update the requests counter while the navigation menu is open.
-        Activity activity = this;
+        // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
+        optionsMenu = menu;
 
-        mainWebView.setWebViewClient(new WebViewClient() {
-            // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
-            // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
-            @SuppressWarnings("deprecation")
-            @Override
-            public boolean shouldOverrideUrlLoading(WebView view, String url) {
-                if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
-                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-                    formattedUrlString = "";
+        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
+        updatePrivacyIcons(false);
 
-                    // Apply the domain settings for the new URL.  `applyDomainSettings` doesn't do anything if the domain has not changed.
-                    boolean userAgentChanged = applyDomainSettings(url, true, false);
+        // Get handles for the menu items.
+        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
+        MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
 
-                    // Check if the user agent has changed.
-                    if (userAgentChanged) {
-                        // Manually load the URL.  The changing of the user agent will cause WebView to reload the previous URL.
-                        mainWebView.loadUrl(url, customHeaders);
+        // Only display third-party cookies if API >= 21
+        toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
 
-                        // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
-                        return true;
-                    } else {
-                        // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
-                        return false;
-                    }
-                } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
-                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
-                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+        // Only display the form data menu items if the API < 26.
+        toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+        clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
 
-                    // Parse the url and set it as the data for the intent.
-                    emailIntent.setData(Uri.parse(url));
+        // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
+        clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
 
-                    // Open the email program in a new task instead of as part of Privacy Browser.
-                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Only show Ad Consent if this is the free flavor.
+        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
 
-                    // Make it so.
-                    startActivity(emailIntent);
+        // Get the shared preference values.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
-                    return true;
-                } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
-                    // Open the dialer and load the phone number, but wait for the user to place the call.
-                    Intent dialIntent = new Intent(Intent.ACTION_DIAL);
+        // Get the dark theme and app bar preferences..
+        boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-                    // Add the phone number to the intent.
-                    dialIntent.setData(Uri.parse(url));
+        // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
+        if (displayAdditionalAppBarIcons) {
+            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        } else { //Do not display the additional icons.
+            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+        }
 
-                    // Open the dialer in a new task instead of as part of Privacy Browser.
-                    dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Replace Refresh with Stop if a URL is already loading.
+        if (currentWebView != null && currentWebView.getProgress() != 100) {
+            // Set the title.
+            refreshMenuItem.setTitle(R.string.stop);
 
-                    // Make it so.
-                    startActivity(dialIntent);
+            // If the icon is displayed in the AppBar, set it according to the theme.
+            if (displayAdditionalAppBarIcons) {
+                if (darkTheme) {
+                    refreshMenuItem.setIcon(R.drawable.close_dark);
+                } else {
+                    refreshMenuItem.setIcon(R.drawable.close_light);
+                }
+            }
+        }
 
-                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
-                    return true;
-                } else {  // Load a system chooser to select an app that can handle the URL.
-                    // Open an app that can handle the URL.
-                    Intent genericIntent = new Intent(Intent.ACTION_VIEW);
+        return true;
+    }
 
-                    // Add the URL to the intent.
-                    genericIntent.setData(Uri.parse(url));
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // Get handles for the menu items.
+        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
+        MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+        MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+        MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+        MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
+        MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
+        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
+        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
+        MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
+        MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
+        MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
+        MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
+        MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
+        MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+        MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
+        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
+        MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
+        MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
+        MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
 
-                    // List all apps that can handle the URL instead of just opening the first one.
-                    genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
+        // Get a handle for the cookie manager.
+        CookieManager cookieManager = CookieManager.getInstance();
 
-                    // Open the app in a new task instead of as part of Privacy Browser.
-                    genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Initialize the current user agent string and the font size.
+        String currentUserAgent = getString(R.string.user_agent_privacy_browser);
+        int fontSize = 100;
 
-                    // Start the app or display a snackbar if no app is available to handle the URL.
-                    try {
-                        startActivity(genericIntent);
-                    } catch (ActivityNotFoundException exception) {
-                        Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
-                    }
+        // Set items that require the current web view to be populated.  It will be null when the program is first opened, as `onPrepareOptionsMenu()` is called before the first WebView is initialized.
+        if (currentWebView != null) {
+            // Set the add or edit domain text.
+            if (currentWebView.getDomainSettingsApplied()) {
+                addOrEditDomain.setTitle(R.string.edit_domain_settings);
+            } else {
+                addOrEditDomain.setTitle(R.string.add_domain_settings);
+            }
 
-                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
-                    return true;
-                }
+            // Get the current user agent from the WebView.
+            currentUserAgent = currentWebView.getSettings().getUserAgentString();
+
+            // Get the current font size from the
+            fontSize = currentWebView.getSettings().getTextZoom();
+
+            // Set the status of the menu item checkboxes.
+            domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
+            saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
+            easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
+            easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
+            fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+            fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+            ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
+            blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+            swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
+            displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
+            nightModeMenuItem.setChecked(currentWebView.getNightMode());
+
+            // Initialize the display names for the blocklists with the number of blocked requests.
+            blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+            easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
+            easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
+            fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
+            fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
+            ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
+            blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
+
+            // Only modify third-party cookies if the API >= 21.
+            if (Build.VERSION.SDK_INT >= 21) {
+                // Set the status of the third-party cookies checkbox.
+                thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
+
+                // Enable third-party cookies if first-party cookies are enabled.
+                thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
             }
 
-            // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
-            @SuppressWarnings("deprecation")
-            @Override
-            public WebResourceResponse shouldInterceptRequest(WebView view, String url){
-                // Create an empty web resource response to be used if the resource request is blocked.
-                WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
+            // Enable DOM Storage if JavaScript is enabled.
+            domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
+        }
 
-                // Reset the whitelist results tracker.
-                whiteListResultStringArray = null;
+        // Set the status of the menu item checkboxes.
+        firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
+        proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
 
-                // Initialize the third party request tracker.
-                boolean isThirdPartyRequest = false;
+        // Enable Clear Cookies if there are any.
+        clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
 
-                // Initialize the current domain string.
-                String currentDomain = "";
+        // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+        String privateDataDirectoryString = getApplicationInfo().dataDir;
 
-                // Nobody is happy when comparing null strings.
-                if (!(formattedUrlString == null) && !(url == null)) {
-                    // Get the domain strings to URIs.
-                    Uri currentDomainUri = Uri.parse(formattedUrlString);
-                    Uri requestDomainUri = Uri.parse(url);
+        // Get a count of the number of files in the Local Storage directory.
+        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
+        int localStorageDirectoryNumberOfFiles = 0;
+        if (localStorageDirectory.exists()) {
+            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
+        }
 
-                    // Get the domain host names.
-                    String currentBaseDomain = currentDomainUri.getHost();
-                    String requestBaseDomain = requestDomainUri.getHost();
+        // Get a count of the number of files in the IndexedDB directory.
+        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
+        int indexedDBDirectoryNumberOfFiles = 0;
+        if (indexedDBDirectory.exists()) {
+            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
+        }
 
-                    // Update the current domain variable.
-                    currentDomain = currentBaseDomain;
+        // Enable Clear DOM Storage if there is any.
+        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
 
-                    // Only compare the current base domain and the request base domain if neither is null.
-                    if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
-                        // Determine the current base domain.
-                        while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
-                        }
+        // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
+        if (Build.VERSION.SDK_INT < 26) {
+            // Get the WebView database.
+            WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
 
-                        // Determine the request base domain.
-                        while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
-                        }
+            // Enable the clear form data menu item if there is anything to clear.
+            clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
+        }
 
-                        // Update the third party request tracker.
-                        isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
-                    }
-                }
+        // Enable Clear Data if any of the submenu items are enabled.
+        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
 
-                // Block third-party requests if enabled.
-                if (isThirdPartyRequest && blockAllThirdPartyRequests) {
-                    // Increment the blocked requests counters.
-                    blockedRequests++;
-                    thirdPartyBlockedRequests++;
-
-                    // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                    activity.runOnUiThread(() -> {
-                        navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                        blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
-                    });
+        // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
+        fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
 
-                    // Add the request to the log.
-                    resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
+        // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
+        if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
+            menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
+        } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
+            menu.findItem(R.id.user_agent_webview_default).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
+            menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
+            menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
+            menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
+            menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
+            menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
+            menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
+            menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
+            menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
+            menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
+            menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
+        } else {  // Custom user agent.
+            menu.findItem(R.id.user_agent_custom).setChecked(true);
+        }
 
-                    // Return an empty web resource response.
-                    return emptyWebResourceResponse;
-                }
+        // Instantiate the font size title and the selected font size menu item.
+        String fontSizeTitle;
+        MenuItem selectedFontSizeMenuItem;
 
-                // Check UltraPrivacy if it is enabled.
-                if (ultraPrivacyEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        ultraPrivacyBlockedRequests++;
+        // Prepare the font size title and current size menu item.
+        switch (fontSize) {
+            case 25:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
+                break;
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
-                        });
+            case 50:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
+                break;
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
+            case 75:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
+                break;
 
-                    // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
-                    if (whiteListResultStringArray != null) {
-                        // Add a whitelist entry to the resource requests array.
-                        resourceRequests.add(whiteListResultStringArray);
+            case 100:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+                break;
 
-                        // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
-                        return null;
-                    }
-                }
+            case 125:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
+                break;
 
-                // Check EasyList if it is enabled.
-                if (easyListEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        easyListBlockedRequests++;
+            case 150:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
+                break;
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
-                        });
+            case 175:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
+                break;
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+            case 200:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
+                break;
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
-                }
+            default:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+                break;
+        }
 
-                // Check EasyPrivacy if it is enabled.
-                if (easyPrivacyEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        easyPrivacyBlockedRequests++;
+        // Set the font size title and select the current size menu item.
+        fontSizeMenuItem.setTitle(fontSizeTitle);
+        selectedFontSizeMenuItem.setChecked(true);
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
-                        });
+        // Run all the other default commands.
+        super.onPrepareOptionsMenu(menu);
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+        // Display the menu.
+        return true;
+    }
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
-                }
+    @Override
+    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
+    @SuppressLint("SetJavaScriptEnabled")
+    public boolean onOptionsItemSelected(MenuItem menuItem) {
+        // Reenter full screen browsing mode if it was interrupted by the options menu.  <https://redmine.stoutner.com/issues/389>
+        if (inFullScreenBrowsingMode) {
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-                // Check Fanboy’s Annoyance List if it is enabled.
-                if (fanboysAnnoyanceListEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        fanboysAnnoyanceListBlockedRequests++;
+            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
-                        });
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        }
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+        // Get the selected menu item ID.
+        int menuItemId = menuItem.getItemId();
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
-                } else if (fanboysSocialBlockingListEnabled){  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        fanboysSocialBlockingListBlockedRequests++;
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
-                        });
+        // Get a handle for the cookie manager.
+        CookieManager cookieManager = CookieManager.getInstance();
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+        // Run the commands that correlate to the selected menu item.
+        switch (menuItemId) {
+            case R.id.toggle_javascript:
+                // Toggle the JavaScript status.
+                currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
-                }
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
 
-                // Add the request to the log because it hasn't been processed by any of the previous checks.
-                if (whiteListResultStringArray != null ) {  // The request was processed by a whitelist.
-                    resourceRequests.add(whiteListResultStringArray);
-                } else {  // The request didn't match any blocklist entry.  Log it as a default request.
-                    resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
+                // Display a `Snackbar`.
+                if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
+                } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
+                } else {  // Privacy mode.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                 }
 
-                // The resource request has not been blocked.  `return null` loads the requested resource.
-                return null;
-            }
-
-            // Handle HTTP authentication requests.
-            @Override
-            public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
-                // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
-                httpAuthHandler = handler;
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                // Display the HTTP authentication dialog.
-                AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
-                httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
-            }
+            case R.id.add_or_edit_domain:
+                if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
+                    // Reapply the domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
 
-            // Update the URL in urlTextBox when the page starts to load.
-            @Override
-            public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                // Reset the list of resource requests.
-                resourceRequests.clear();
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
-                // Initialize the counters for requests blocked by each blocklist.
-                blockedRequests = 0;
-                easyListBlockedRequests = 0;
-                easyPrivacyBlockedRequests = 0;
-                fanboysAnnoyanceListBlockedRequests = 0;
-                fanboysSocialBlockingListBlockedRequests = 0;
-                ultraPrivacyBlockedRequests = 0;
-                thirdPartyBlockedRequests = 0;
+                    // Add the extra information to the intent.
+                    domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
+                    domainsIntent.putExtra("close_on_back", true);
+                    domainsIntent.putExtra("current_url", currentWebView.getUrl());
+
+                    // Get the current certificate.
+                    SslCertificate sslCertificate = currentWebView.getCertificate();
+
+                    // Check to see if the SSL certificate is populated.
+                    if (sslCertificate != null) {
+                        // Extract the certificate to strings.
+                        String issuedToCName = sslCertificate.getIssuedTo().getCName();
+                        String issuedToOName = sslCertificate.getIssuedTo().getOName();
+                        String issuedToUName = sslCertificate.getIssuedTo().getUName();
+                        String issuedByCName = sslCertificate.getIssuedBy().getCName();
+                        String issuedByOName = sslCertificate.getIssuedBy().getOName();
+                        String issuedByUName = sslCertificate.getIssuedBy().getUName();
+                        long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+                        long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+
+                        // Add the certificate to the intent.
+                        domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+                        domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+                        domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+                        domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+                        domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+                        domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+                        domainsIntent.putExtra("ssl_start_date", startDateLong);
+                        domainsIntent.putExtra("ssl_end_date", endDateLong);
+                    }
 
-                // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
-                if (nightMode) {
-                    mainWebView.setVisibility(View.INVISIBLE);
-                }
+                    // Check to see if the current IP addresses have been received.
+                    if (currentWebView.hasCurrentIpAddresses()) {
+                        // Add the current IP addresses to the intent.
+                        domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                    }
 
-                // Hide the keyboard.  `0` indicates no additional flags.
-                inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+                    // Make it so.
+                    startActivity(domainsIntent);
+                } else {  // Add a new domain.
+                    // Apply the new domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
 
-                // Check to see if Privacy Browser is waiting on Orbot.
-                if (!waitingForOrbot) {  // We are not waiting on Orbot, so we need to process the URL.
-                    // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
-                    formattedUrlString = url;
+                    // Get the current domain
+                    Uri currentUri = Uri.parse(currentWebView.getUrl());
+                    String currentDomain = currentUri.getHost();
 
-                    // Display the formatted URL text.
-                    urlTextBox.setText(formattedUrlString);
+                    // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+                    DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
 
-                    // Apply text highlighting to `urlTextBox`.
-                    highlightUrlText();
+                    // Create the domain and store the database ID.
+                    int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
 
-                    // Apply any custom domain settings if the URL was loaded by navigating history.
-                    if (navigatingHistory) {
-                        // Apply the domain settings.
-                        boolean userAgentChanged = applyDomainSettings(url, true, false);
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
-                        // Reset `navigatingHistory`.
-                        navigatingHistory = false;
+                    // Add the extra information to the intent.
+                    domainsIntent.putExtra("load_domain", newDomainDatabaseId);
+                    domainsIntent.putExtra("close_on_back", true);
+                    domainsIntent.putExtra("current_url", currentWebView.getUrl());
+
+                    // Get the current certificate.
+                    SslCertificate sslCertificate = currentWebView.getCertificate();
+
+                    // Check to see if the SSL certificate is populated.
+                    if (sslCertificate != null) {
+                        // Extract the certificate to strings.
+                        String issuedToCName = sslCertificate.getIssuedTo().getCName();
+                        String issuedToOName = sslCertificate.getIssuedTo().getOName();
+                        String issuedToUName = sslCertificate.getIssuedTo().getUName();
+                        String issuedByCName = sslCertificate.getIssuedBy().getCName();
+                        String issuedByOName = sslCertificate.getIssuedBy().getOName();
+                        String issuedByUName = sslCertificate.getIssuedBy().getUName();
+                        long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+                        long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+
+                        // Add the certificate to the intent.
+                        domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+                        domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+                        domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+                        domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+                        domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+                        domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+                        domainsIntent.putExtra("ssl_start_date", startDateLong);
+                        domainsIntent.putExtra("ssl_end_date", endDateLong);
+                    }
 
-                        // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
-                        if (userAgentChanged) {
-                            loadUrl(formattedUrlString);
-                        }
+                    // Check to see if the current IP addresses have been received.
+                    if (currentWebView.hasCurrentIpAddresses()) {
+                        // Add the current IP addresses to the intent.
+                        domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
                     }
 
-                    // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
-                    urlIsLoading = true;
+                    // Make it so.
+                    startActivity(domainsIntent);
+                }
+                return true;
 
-                    // Replace Refresh with Stop if the menu item has been created.  (The WebView typically begins loading before the menu items are instantiated.)
-                    if (refreshMenuItem != null) {
-                        // Set the title.
-                        refreshMenuItem.setTitle(R.string.stop);
+            case R.id.toggle_first_party_cookies:
+                // Switch the first-party cookie status.
+                cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
 
-                        // If the icon is displayed in the AppBar, set it according to the theme.
-                        if (displayAdditionalAppBarIcons) {
-                            if (darkTheme) {
-                                refreshMenuItem.setIcon(R.drawable.close_dark);
-                            } else {
-                                refreshMenuItem.setIcon(R.drawable.close_light);
-                            }
-                        }
-                    }
-                }
-            }
+                // Store the first-party cookie status.
+                currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
 
-            // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
-            @Override
-            public void onPageFinished(WebView view, String url) {
-                // Reset the wide view port if it has been turned off by the waiting for Orbot message.
-                if (!waitingForOrbot) {
-                    // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
-                    mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
-                }
+                // Update the menu checkbox.
+                menuItem.setChecked(cookieManager.acceptCookie());
+
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
 
-                // Flush any cookies to persistent storage.  `CookieManager` has become very lazy about flushing cookies in recent versions.
-                if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.flush();
+                // Display a snackbar.
+                if (cookieManager.acceptCookie()) {  // First-party cookies are enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+                } else {  // Privacy mode.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                 }
 
-                // Update the Refresh menu item if it has been created.
-                if (refreshMenuItem != null) {
-                    // Reset the Refresh title.
-                    refreshMenuItem.setTitle(R.string.refresh);
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                    // If the icon is displayed in the AppBar, reset it according to the theme.
-                    if (displayAdditionalAppBarIcons) {
-                        if (darkTheme) {
-                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
-                        } else {
-                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
-                        }
+            case R.id.toggle_third_party_cookies:
+                if (Build.VERSION.SDK_INT >= 21) {
+                    // Switch the status of thirdPartyCookiesEnabled.
+                    cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
+
+                    // Update the menu checkbox.
+                    menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
+
+                    // Display a snackbar.
+                    if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
+                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                    } else {
+                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
                     }
-                }
 
-                // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
-                urlIsLoading = false;
+                    // Reload the current WebView.
+                    currentWebView.reload();
+                } // Else do nothing because SDK < 21.
+                return true;
 
-                // Clear the cache and history if Incognito Mode is enabled.
-                if (incognitoModeEnabled) {
-                    // Clear the cache.  `true` includes disk files.
-                    mainWebView.clearCache(true);
+            case R.id.toggle_dom_storage:
+                // Toggle the status of domStorageEnabled.
+                currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
 
-                    // Clear the back/forward history.
-                    mainWebView.clearHistory();
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
 
-                    // Manually delete cache folders.
-                    try {
-                        // Delete the main cache directory.
-                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
+                // Update the privacy icon.  `true` refreshes the app bar icons.
+                updatePrivacyIcons(true);
 
-                        // Delete the secondary `Service Worker` cache directory.
-                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
-                        privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
-                    } catch (IOException e) {
-                        // Do nothing if an error is thrown.
-                    }
+                // Display a snackbar.
+                if (currentWebView.getSettings().getDomStorageEnabled()) {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
+                } else {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
                 }
 
-                // Update `urlTextBox` and apply domain settings if not waiting on Orbot.
-                if (!waitingForOrbot) {
-                    // Check to see if `WebView` has set `url` to be `about:blank`.
-                    if (url.equals("about:blank")) {  // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
-                        // Set `formattedUrlString` to `""`.
-                        formattedUrlString = "";
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        urlTextBox.setText(formattedUrlString);
+            // Form data can be removed once the minimum API >= 26.
+            case R.id.toggle_save_form_data:
+                // Switch the status of saveFormDataEnabled.
+                currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
 
-                        // Request focus for `urlTextBox`.
-                        urlTextBox.requestFocus();
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
 
-                        // Display the keyboard.
-                        inputMethodManager.showSoftInput(urlTextBox, 0);
+                // Display a snackbar.
+                if (currentWebView.getSettings().getSaveFormData()) {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
+                } else {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
+                }
 
-                        // Apply the domain settings.  This clears any settings from the previous domain.
-                        applyDomainSettings(formattedUrlString, true, false);
-                    } else {  // `WebView` has loaded a webpage.
-                        // Set `formattedUrlString`.
-                        formattedUrlString = url;
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
 
-                        // Only update `urlTextBox` if the user is not typing in it.
-                        if (!urlTextBox.hasFocus()) {
-                            // Display the formatted URL text.
-                            urlTextBox.setText(formattedUrlString);
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                            // Apply text highlighting to `urlTextBox`.
-                            highlightUrlText();
-                        }
-                    }
+            case R.id.clear_cookies:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
+                                    // Delete the cookies, which command varies by SDK.
+                                    if (Build.VERSION.SDK_INT < 21) {
+                                        cookieManager.removeAllCookie();
+                                    } else {
+                                        cookieManager.removeAllCookies(null);
+                                    }
+                                }
+                            }
+                        })
+                        .show();
+                return true;
 
-                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
-                    sslCertificate = mainWebView.getCertificate();
-
-                    // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
-                    // Also ignore if changes in the user agent causes an error while navigating history.
-                    if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
-                        // Initialize the current SSL certificate variables.
-                        String currentWebsiteIssuedToCName = "";
-                        String currentWebsiteIssuedToOName = "";
-                        String currentWebsiteIssuedToUName = "";
-                        String currentWebsiteIssuedByCName = "";
-                        String currentWebsiteIssuedByOName = "";
-                        String currentWebsiteIssuedByUName = "";
-                        Date currentWebsiteSslStartDate = null;
-                        Date currentWebsiteSslEndDate = null;
-
-
-                        // Extract the individual pieces of information from the current website SSL certificate if it is not null.
-                        if (sslCertificate != null) {
-                            currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
-                            currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
-                            currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
-                            currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
-                            currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
-                            currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
-                            currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
-                            currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
-                        }
+            case R.id.clear_dom_storage:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
+                                    // Delete the DOM Storage.
+                                    WebStorage webStorage = WebStorage.getInstance();
+                                    webStorage.deleteAllData();
+
+                                    // Initialize a handler to manually delete the DOM storage files and directories.
+                                    Handler deleteDomStorageHandler = new Handler();
+
+                                    // Setup a runnable to manually delete the DOM storage files and directories.
+                                    Runnable deleteDomStorageRunnable = () -> {
+                                        try {
+                                            // Get a handle for the runtime.
+                                            Runtime runtime = Runtime.getRuntime();
+
+                                            // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
+                                            // which links to `/data/data/com.stoutner.privacybrowser.standard`.
+                                            String privateDataDirectoryString = getApplicationInfo().dataDir;
+
+                                            // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                                            Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+                                            // Multiple commands must be used because `Runtime.exec()` does not like `*`.
+                                            Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                                            Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+                                            Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+                                            Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+
+                                            // Wait for the processes to finish.
+                                            deleteLocalStorageProcess.waitFor();
+                                            deleteIndexProcess.waitFor();
+                                            deleteQuotaManagerProcess.waitFor();
+                                            deleteQuotaManagerJournalProcess.waitFor();
+                                            deleteDatabasesProcess.waitFor();
+                                        } catch (Exception exception) {
+                                            // Do nothing if an error is thrown.
+                                        }
+                                    };
 
-                        // Initialize `String` variables to store the SSL certificate dates.  `Strings` are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
-                        String currentWebsiteSslStartDateString = "";
-                        String currentWebsiteSslEndDateString = "";
-                        String pinnedDomainSslStartDateString = "";
-                        String pinnedDomainSslEndDateString = "";
+                                    // Manually delete the DOM storage files after 200 milliseconds.
+                                    deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
+                                }
+                            }
+                        })
+                        .show();
+                return true;
 
-                        // Convert the `Dates` to `Strings` if they are not `null`.
-                        if (currentWebsiteSslStartDate != null) {
-                            currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
-                        }
+            // Form data can be remove once the minimum API >= 26.
+            case R.id.clear_form_data:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
+                                    // Delete the form data.
+                                    WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
+                                    mainWebViewDatabase.clearFormData();
+                                }
+                            }
+                        })
+                        .show();
+                return true;
 
-                        if (currentWebsiteSslEndDate != null) {
-                            currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
-                        }
+            case R.id.easylist:
+                // Toggle the EasyList status.
+                currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
 
-                        if (pinnedDomainSslStartDate != null) {
-                            pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
-                        }
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
 
-                        if (pinnedDomainSslEndDate != null) {
-                            pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
-                        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // Check to see if the pinned SSL certificate matches the current website certificate.
-                        if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
-                                !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
-                                !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
-                                !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
-                            // The pinned SSL certificate doesn't match the current domain certificate.
-                            //Display the pinned SSL certificate mismatch `AlertDialog`.
-                            AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
-                            pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
-                        }
-                    }
-                }
-            }
+            case R.id.easyprivacy:
+                // Toggle the EasyPrivacy status.
+                currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
 
-            // Handle SSL Certificate errors.
-            @Override
-            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-                // Get the current website SSL certificate.
-                SslCertificate currentWebsiteSslCertificate = error.getCertificate();
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
 
-                // Extract the individual pieces of information from the current website SSL certificate.
-                String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
-                String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
-                String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
-                String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
-                String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
-                String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
-                Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
-                Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
-                if (pinnedDomainSslCertificate &&
-                        currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) &&
-                        currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) &&
-                        currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
-                        currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {
-                    // An SSL certificate is pinned and matches the current domain certificate.
-                    // Proceed to the website without displaying an error.
-                    handler.proceed();
-                } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
-                    // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
-                    sslErrorHandler = handler;
+            case R.id.fanboys_annoyance_list:
+                // Toggle Fanboy's Annoyance List status.
+                currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
-                    // Display the SSL error `AlertDialog`.
-                    AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
-                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
-                }
-            }
-        });
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
-        // Get the intent that started the app.
-        Intent launchingIntent = getIntent();
+                // Update the staus of Fanboy's Social Blocking List.
+                MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
+                fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
-        // Get the information from the intent.
-        String launchingIntentAction = launchingIntent.getAction();
-        Uri launchingIntentUriData = launchingIntent.getData();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // If the intent action is a web search, perform the search.
-        if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
-            // Create an encoded URL string.
-            String encodedUrlString;
+            case R.id.fanboys_social_blocking_list:
+                // Toggle Fanboy's Social Blocking List status.
+                currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
 
-            // Sanitize the search input and convert it to a search.
-            try {
-                encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                encodedUrlString = "";
-            }
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
 
-            // Add the base search URL.
-            formattedUrlString = searchURL + encodedUrlString;
-        } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
-            // Set the formatted URL string.
-            formattedUrlString = launchingIntentUriData.toString();
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Load the website if not waiting for Orbot to connect.
-        if (!waitingForOrbot) {
-            loadUrl(formattedUrlString);
-        }
-    }
+            case R.id.ultraprivacy:
+                // Toggle the UltraPrivacy status.
+                currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
 
-    @Override
-    protected void onNewIntent(Intent intent) {
-        // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
-        setIntent(intent);
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
 
-        // Get the information from the intent.
-        String intentAction = intent.getAction();
-        Uri intentUriData = intent.getData();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // If the intent action is a web search, perform the search.
-        if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
-            // Create an encoded URL string.
-            String encodedUrlString;
+            case R.id.block_all_third_party_requests:
+                //Toggle the third-party requests blocker status.
+                currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
 
-            // Sanitize the search input and convert it to a search.
-            try {
-                encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                encodedUrlString = "";
-            }
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
 
-            // Add the base search URL.
-            formattedUrlString = searchURL + encodedUrlString;
-        } else if (intentUriData != null){  // Check to see if the intent contains a new URL.
-            // Set the formatted URL string.
-            formattedUrlString = intentUriData.toString();
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Load the URL.
-        loadUrl(formattedUrlString);
+            case R.id.user_agent_privacy_browser:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
 
-        // Close the navigation drawer if it is open.
-        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
-            drawerLayout.closeDrawer(GravityCompat.START);
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Close the bookmarks drawer if it is open.
-        if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
-            drawerLayout.closeDrawer(GravityCompat.END);
-        }
+            case R.id.user_agent_webview_default:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString("");
 
-        // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
-        mainWebView.requestFocus();
-    }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-    @Override
-    public void onRestart() {
-        // Run the default commands.
-        super.onRestart();
+            case R.id.user_agent_firefox_on_android:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
 
-        // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
-        if (proxyThroughOrbot) {
-            // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
-            Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-            // Send the intent to the Orbot package.
-            orbotIntent.setPackage("org.torproject.android");
+            case R.id.user_agent_chrome_on_android:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
 
-            // Make it so.
-            sendBroadcast(orbotIntent);
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Apply the app settings if returning from the Settings activity..
-        if (reapplyAppSettingsOnRestart) {
-            // Apply the app settings.
-            applyAppSettings();
+            case R.id.user_agent_safari_on_ios:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
 
-            // Reload the webpage if displaying of images has been disabled in the Settings activity.
-            if (reloadOnRestart) {
-                // Reload `mainWebView`.
-                mainWebView.reload();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                // Reset `reloadOnRestartBoolean`.
-                reloadOnRestart = false;
-            }
+            case R.id.user_agent_firefox_on_linux:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
 
-            // Reset the return from settings flag.
-            reapplyAppSettingsOnRestart = false;
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Apply the domain settings if returning from the Domains activity.
-        if (reapplyDomainSettingsOnRestart) {
-            // Reapply the domain settings.
-            applyDomainSettings(formattedUrlString, false, true);
+            case R.id.user_agent_chromium_on_linux:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
 
-            // Reset `reapplyDomainSettingsOnRestart`.
-            reapplyDomainSettingsOnRestart = false;
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Load the URL on restart to apply changes to night mode.
-        if (loadUrlOnRestart) {
-            // Load the current `formattedUrlString`.
-            loadUrl(formattedUrlString);
+            case R.id.user_agent_firefox_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
 
-            // Reset `loadUrlOnRestart.
-            loadUrlOnRestart = false;
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Update the bookmarks drawer if returning from the Bookmarks activity.
-        if (restartFromBookmarksActivity) {
-            // Close the bookmarks drawer.
-            drawerLayout.closeDrawer(GravityCompat.END);
+            case R.id.user_agent_chrome_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
 
-            // Reload the bookmarks drawer.
-            loadBookmarksFolder();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-            // Reset `restartFromBookmarksActivity`.
-            restartFromBookmarksActivity = false;
-        }
+            case R.id.user_agent_edge_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
 
-        // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
-        updatePrivacyIcons(true);
-    }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
-    @Override
-    public void onResume() {
-        // Run the default commands.
-        super.onResume();
+            case R.id.user_agent_internet_explorer_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
 
-        // Resume JavaScript (if enabled).
-        mainWebView.resumeTimers();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Resume `mainWebView`.
-        mainWebView.onResume();
+            case R.id.user_agent_safari_on_macos:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
 
-        // Resume the adView for the free flavor.
-        if (BuildConfig.FLAVOR.contentEquals("free")) {
-            // Resume the ad.
-            AdHelper.resumeAd(findViewById(R.id.adview));
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Display a message to the user if waiting for Orbot.
-        if (waitingForOrbot && !orbotStatus.equals("ON")) {
-            // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
-            mainWebView.getSettings().setUseWideViewPort(false);
+            case R.id.user_agent_custom:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
 
-            // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
-            mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
-        }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        if (displayingFullScreenVideo) {
-            // Remove the translucent overlays.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            case R.id.font_size_twenty_five_percent:
+                currentWebView.getSettings().setTextZoom(25);
+                return true;
 
-            // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
-            drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+            case R.id.font_size_fifty_percent:
+                currentWebView.getSettings().setTextZoom(50);
+                return true;
 
-            /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-        }
-    }
+            case R.id.font_size_seventy_five_percent:
+                currentWebView.getSettings().setTextZoom(75);
+                return true;
 
-    @Override
-    public void onPause() {
-        // Run the default commands.
-        super.onPause();
+            case R.id.font_size_one_hundred_percent:
+                currentWebView.getSettings().setTextZoom(100);
+                return true;
 
-        // Pause `mainWebView`.
-        mainWebView.onPause();
+            case R.id.font_size_one_hundred_twenty_five_percent:
+                currentWebView.getSettings().setTextZoom(125);
+                return true;
 
-        // Stop all JavaScript.
-        mainWebView.pauseTimers();
+            case R.id.font_size_one_hundred_fifty_percent:
+                currentWebView.getSettings().setTextZoom(150);
+                return true;
 
-        // Pause the ad or it will continue to consume resources in the background on the free flavor.
-        if (BuildConfig.FLAVOR.contentEquals("free")) {
-            // Pause the ad.
-            AdHelper.pauseAd(findViewById(R.id.adview));
-        }
-    }
+            case R.id.font_size_one_hundred_seventy_five_percent:
+                currentWebView.getSettings().setTextZoom(175);
+                return true;
 
-    @Override
-    public void onDestroy() {
-        // Unregister the Orbot status broadcast receiver.
-        this.unregisterReceiver(orbotStatusBroadcastReceiver);
+            case R.id.font_size_two_hundred_percent:
+                currentWebView.getSettings().setTextZoom(200);
+                return true;
 
-        // Close the bookmarks cursor and database.
-        bookmarksCursor.close();
-        bookmarksDatabaseHelper.close();
+            case R.id.swipe_to_refresh:
+                // Toggle the stored status of swipe to refresh.
+                currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
+
+                // Get a handle for the swipe refresh layout.
+                SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
+                // Update the swipe refresh layout.
+                if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
+                    if (Build.VERSION.SDK_INT >= 23) {  // For API >= 23, the status of the scroll refresh listener is continuously updated by the on scroll change listener.
+                        // Only enable the swipe refresh layout if the WebView is scrolled to the top.
+                        swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                    } else {  // For API < 23, the swipe refresh layout is always enabled.
+                        // Enable the swipe refresh layout.
+                        swipeRefreshLayout.setEnabled(true);
+                    }
+                } else {  // Swipe to refresh is disabled.
+                    // Disable the swipe refresh layout.
+                    swipeRefreshLayout.setEnabled(false);
+                }
+                return true;
 
-        // Run the default commands.
-        super.onDestroy();
-    }
+            case R.id.display_images:
+                if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
+                    // Disable loading of images.
+                    currentWebView.getSettings().setLoadsImagesAutomatically(false);
 
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        // Inflate the menu; this adds items to the action bar if it is present.
-        getMenuInflater().inflate(R.menu.webview_options_menu, menu);
+                    // Reload the website to remove existing images.
+                    currentWebView.reload();
+                } else {  // Images are not currently loaded automatically.
+                    // Enable loading of images.  Missing images will be loaded without the need for a reload.
+                    currentWebView.getSettings().setLoadsImagesAutomatically(true);
+                }
+                return true;
 
-        // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
-        mainMenu = menu;
+            case R.id.night_mode:
+                // Toggle night mode.
+                currentWebView.setNightMode(!currentWebView.getNightMode());
 
-        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
-        updatePrivacyIcons(false);
+                // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
+                if (currentWebView.getNightMode()) {  // Night mode is enabled, which requires JavaScript.
+                    // Enable JavaScript.
+                    currentWebView.getSettings().setJavaScriptEnabled(true);
+                } else if (currentWebView.getDomainSettingsApplied()) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
+                    // Apply the JavaScript preference that was stored the last time domain settings were loaded.
+                    currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
+                } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
+                    // Apply the JavaScript preference.
+                    currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
+                }
 
-        // Get handles for the menu items.
-        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
-        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        refreshMenuItem = menu.findItem(R.id.refresh);
-        blocklistsMenuItem = menu.findItem(R.id.blocklists);
-        easyListMenuItem = menu.findItem(R.id.easylist);
-        easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
-        fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
-        fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
-        ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
-        blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
-        MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
+                // Update the privacy icons.
+                updatePrivacyIcons(false);
 
-        // Only display third-party cookies if API >= 21
-        toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
+                // Reload the website.
+                currentWebView.reload();
+                return true;
 
-        // Only display the form data menu items if the API < 26.
-        toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
-        clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+            case R.id.find_on_page:
+                // Get a handle for the views.
+                Toolbar toolbar = findViewById(R.id.toolbar);
+                LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+                EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
 
-        // Only show Ad Consent if this is the free flavor.
-        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
+                // Hide the toolbar.
+                toolbar.setVisibility(View.GONE);
 
-        // Get the shared preference values.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+                // Show the find on page linear layout.
+                findOnPageLinearLayout.setVisibility(View.VISIBLE);
 
-        // Get the status of the additional AppBar icons.
-        displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+                // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
+                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+                findOnPageEditText.postDelayed(() -> {
+                    // Set the focus on `findOnPageEditText`.
+                    findOnPageEditText.requestFocus();
 
-        // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
-        if (displayAdditionalAppBarIcons) {
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-        } else { //Do not display the additional icons.
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-        }
+                    // Get a handle for the input method manager.
+                    InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // Replace Refresh with Stop if a URL is already loading.
-        if (urlIsLoading) {
-            // Set the title.
-            refreshMenuItem.setTitle(R.string.stop);
+                    // Remove the lint warning below that the input method manager might be null.
+                    assert inputMethodManager != null;
 
-            // If the icon is displayed in the AppBar, set it according to the theme.
-            if (displayAdditionalAppBarIcons) {
-                if (darkTheme) {
-                    refreshMenuItem.setIcon(R.drawable.close_dark);
-                } else {
-                    refreshMenuItem.setIcon(R.drawable.close_light);
+                    // Display the keyboard.  `0` sets no input flags.
+                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
+                }, 200);
+                return true;
+
+            case R.id.view_source:
+                // Create an intent to launch the view source activity.
+                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+
+                // Add the variables to the intent.
+                viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
+                viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
+
+                // Make it so.
+                startActivity(viewSourceIntent);
+                return true;
+
+            case R.id.share_url:
+                // Setup the share string.
+                String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
+
+                // Create the share intent.
+                Intent shareIntent = new Intent(Intent.ACTION_SEND);
+                shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
+                shareIntent.setType("text/plain");
+
+                // Make it so.
+                startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
+                return true;
+
+            case R.id.print:
+                // Get a print manager instance.
+                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+
+                // Remove the lint error below that print manager might be null.
+                assert printManager != null;
+
+                // Create a print document adapter from the current WebView.
+                PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
+
+                // Print the document.
+                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+                return true;
+
+            case R.id.open_with_app:
+                openWithApp(currentWebView.getUrl());
+                return true;
+
+            case R.id.open_with_browser:
+                openWithBrowser(currentWebView.getUrl());
+                return true;
+
+            case R.id.add_to_homescreen:
+                // Instantiate the create home screen shortcut dialog.
+                DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
+                        currentWebView.getFavoriteOrDefaultIcon());
+
+                // Show the create home screen shortcut dialog.
+                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+                return true;
+
+            case R.id.proxy_through_orbot:
+                // Toggle the proxy through Orbot variable.
+                proxyThroughOrbot = !proxyThroughOrbot;
+
+                // Apply the proxy through Orbot settings.
+                applyProxyThroughOrbot(true);
+                return true;
+
+            case R.id.refresh:
+                if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
+                    // Reload the current WebView.
+                    currentWebView.reload();
+                } else {  // The stop button was pushed.
+                    // Stop the loading of the WebView.
+                    currentWebView.stopLoading();
                 }
-            }
-        }
+                return true;
 
-        return true;
+            case R.id.ad_consent:
+                // Display the ad consent dialog.
+                DialogFragment adConsentDialogFragment = new AdConsentDialog();
+                adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
+                return true;
+
+            default:
+                // Don't consume the event.
+                return super.onOptionsItemSelected(menuItem);
+        }
     }
 
+    // removeAllCookies is deprecated, but it is required for API < 21.
     @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        // Get handles for the menu items.
-        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
-        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
-        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
-        MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
-        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
-        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
-        MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
-        MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
-        MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
+    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
+        // Get the menu item ID.
+        int menuItemId = menuItem.getItemId();
 
-        // Set the text for the domain menu item.
-        if (domainSettingsApplied) {
-            addOrEditDomain.setTitle(R.string.edit_domain_settings);
-        } else {
-            addOrEditDomain.setTitle(R.string.add_domain_settings);
-        }
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Set the status of the menu item checkboxes.
-        toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
-        toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
-        toggleDomStorageMenuItem.setChecked(domStorageEnabled);
-        toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled);  // Form data can be removed once the minimum API >= 26.
-        easyListMenuItem.setChecked(easyListEnabled);
-        easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
-        fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
-        fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
-        ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
-        blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
-        swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
-        displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
-        nightModeMenuItem.setChecked(nightMode);
-        proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
+        // Run the commands that correspond to the selected menu item.
+        switch (menuItemId) {
+            case R.id.close_tab:
+                // Get a handle for the tab layout and the view pager.
+                TabLayout tabLayout = findViewById(R.id.tablayout);
+                ViewPager webViewPager = findViewById(R.id.webviewpager);
 
-        // Enable third-party cookies if first-party cookies are enabled.
-        toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
+                // Get the current tab number.
+                int currentTabNumber = tabLayout.getSelectedTabPosition();
 
-        // Enable DOM Storage if JavaScript is enabled.
-        toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
+                // Delete the current tab.
+                tabLayout.removeTabAt(currentTabNumber);
 
-        // Enable Clear Cookies if there are any.
-        clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
+                // Delete the current page.  If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset.
+                if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
+                    setCurrentWebView(currentTabNumber);
+                }
+                break;
 
-        // Get a count of the number of files in the Local Storage directory.
-        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
-        int localStorageDirectoryNumberOfFiles = 0;
-        if (localStorageDirectory.exists()) {
-            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
-        }
+            case R.id.clear_and_exit:
+                // Close the bookmarks cursor and database.
+                bookmarksCursor.close();
+                bookmarksDatabaseHelper.close();
 
-        // Get a count of the number of files in the IndexedDB directory.
-        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
-        int indexedDBDirectoryNumberOfFiles = 0;
-        if (indexedDBDirectory.exists()) {
-            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
+                // Get the status of the clear everything preference.
+                boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
+
+                // Get a handle for the runtime.
+                Runtime runtime = Runtime.getRuntime();
+
+                // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
+                // which links to `/data/data/com.stoutner.privacybrowser.standard`.
+                String privateDataDirectoryString = getApplicationInfo().dataDir;
+
+                // Clear cookies.
+                if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
+                    // The command to remove cookies changed slightly in API 21.
+                    if (Build.VERSION.SDK_INT >= 21) {
+                        CookieManager.getInstance().removeAllCookies(null);
+                    } else {
+                        CookieManager.getInstance().removeAllCookie();
+                    }
+
+                    // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    try {
+                        // Two commands must be used because `Runtime.exec()` does not like `*`.
+                        Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
+                        Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
+
+                        // Wait until the processes have finished.
+                        deleteCookiesProcess.waitFor();
+                        deleteCookiesJournalProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
+
+                // Clear DOM storage.
+                if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
+                    // Ask `WebStorage` to clear the DOM storage.
+                    WebStorage webStorage = WebStorage.getInstance();
+                    webStorage.deleteAllData();
+
+                    // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    try {
+                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                        Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+                        // Multiple commands must be used because `Runtime.exec()` does not like `*`.
+                        Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                        Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+                        Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+                        Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+
+                        // Wait until the processes have finished.
+                        deleteLocalStorageProcess.waitFor();
+                        deleteIndexProcess.waitFor();
+                        deleteQuotaManagerProcess.waitFor();
+                        deleteQuotaManagerJournalProcess.waitFor();
+                        deleteDatabaseProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
+
+                // Clear form data if the API < 26.
+                if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
+                    WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
+                    webViewDatabase.clearFormData();
+
+                    // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    try {
+                        // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
+                        Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
+                        Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
+
+                        // Wait until the processes have finished.
+                        deleteWebDataProcess.waitFor();
+                        deleteWebDataJournalProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
+
+                // Clear the cache.
+                if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
+                    // Clear the cache from each WebView.
+                    for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                        // Get the WebView tab fragment.
+                        WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+
+                        // Get the fragment view.
+                        View fragmentView = webViewTabFragment.getView();
+
+                        // Only clear the cache if the WebView exists.
+                        if (fragmentView != null) {
+                            // Get the nested scroll WebView from the tab fragment.
+                            NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+                            // Clear the cache for this WebView.
+                            nestedScrollWebView.clearCache(true);
+                        }
+                    }
+
+                    // Manually delete the cache directories.
+                    try {
+                        // Delete the main cache directory.
+                        Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
+
+                        // Delete the secondary `Service Worker` cache directory.
+                        // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                        Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+
+                        // Wait until the processes have finished.
+                        deleteCacheProcess.waitFor();
+                        deleteServiceWorkerProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
+
+                // Wipe out each WebView.
+                for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                    // Get the WebView tab fragment.
+                    WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+
+                    // Get the fragment view.
+                    View fragmentView = webViewTabFragment.getView();
+
+                    // Only wipe out the WebView if it exists.
+                    if (fragmentView != null) {
+                        // Get the nested scroll WebView from the tab fragment.
+                        NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+                        // Clear SSL certificate preferences for this WebView.
+                        nestedScrollWebView.clearSslPreferences();
+
+                        // Clear the back/forward history for this WebView.
+                        nestedScrollWebView.clearHistory();
+
+                        // Destroy the internal state of `mainWebView`.
+                        nestedScrollWebView.destroy();
+                    }
+                }
+
+                // Clear the custom headers.
+                customHeaders.clear();
+
+                // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
+                // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
+                if (clearEverything) {
+                    try {
+                        // Delete the folder.
+                        Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
+
+                        // Wait until the process has finished.
+                        deleteAppWebviewProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
+
+                // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    finishAndRemoveTask();
+                } else {
+                    finish();
+                }
+
+                // Remove the terminated program from RAM.  The status code is `0`.
+                System.exit(0);
+                break;
+
+            case R.id.home:
+                // Select the homepage based on the proxy through Orbot status.
+                if (proxyThroughOrbot) {
+                    // Load the Tor homepage.
+                    loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
+                } else {
+                    // Load the normal homepage.
+                    loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
+                }
+                break;
+
+            case R.id.back:
+                if (currentWebView.canGoBack()) {
+                    // Reset the current domain name so that navigation works if third-party requests are blocked.
+                    currentWebView.resetCurrentDomainName();
+
+                    // Set navigating history so that the domain settings are applied when the new URL is loaded.
+                    currentWebView.setNavigatingHistory(true);
+
+                    // Load the previous website in the history.
+                    currentWebView.goBack();
+                }
+                break;
+
+            case R.id.forward:
+                if (currentWebView.canGoForward()) {
+                    // Reset the current domain name so that navigation works if third-party requests are blocked.
+                    currentWebView.resetCurrentDomainName();
+
+                    // Set navigating history so that the domain settings are applied when the new URL is loaded.
+                    currentWebView.setNavigatingHistory(true);
+
+                    // Load the next website in the history.
+                    currentWebView.goForward();
+                }
+                break;
+
+            case R.id.history:
+                // Instantiate the URL history dialog.
+                DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
+
+                // Show the URL history dialog.
+                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
+                break;
+
+            case R.id.requests:
+                // Populate the resource requests.
+                RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
+
+                // Create an intent to launch the Requests activity.
+                Intent requestsIntent = new Intent(this, RequestsActivity.class);
+
+                // Add the block third-party requests status to the intent.
+                requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+
+                // Make it so.
+                startActivity(requestsIntent);
+                break;
+
+            case R.id.downloads:
+                // Launch the system Download Manager.
+                Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+
+                // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
+                downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                startActivity(downloadManagerIntent);
+                break;
+
+            case R.id.domains:
+                // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
+                reapplyDomainSettingsOnRestart = true;
+
+                // Launch the domains activity.
+                Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+                // Add the extra information to the intent.
+                domainsIntent.putExtra("current_url", currentWebView.getUrl());
+
+                // Get the current certificate.
+                SslCertificate sslCertificate = currentWebView.getCertificate();
+
+                // Check to see if the SSL certificate is populated.
+                if (sslCertificate != null) {
+                    // Extract the certificate to strings.
+                    String issuedToCName = sslCertificate.getIssuedTo().getCName();
+                    String issuedToOName = sslCertificate.getIssuedTo().getOName();
+                    String issuedToUName = sslCertificate.getIssuedTo().getUName();
+                    String issuedByCName = sslCertificate.getIssuedBy().getCName();
+                    String issuedByOName = sslCertificate.getIssuedBy().getOName();
+                    String issuedByUName = sslCertificate.getIssuedBy().getUName();
+                    long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+                    long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+
+                    // Add the certificate to the intent.
+                    domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+                    domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+                    domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+                    domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+                    domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+                    domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+                    domainsIntent.putExtra("ssl_start_date", startDateLong);
+                    domainsIntent.putExtra("ssl_end_date", endDateLong);
+                }
+
+                // Check to see if the current IP addresses have been received.
+                if (currentWebView.hasCurrentIpAddresses()) {
+                    // Add the current IP addresses to the intent.
+                    domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                }
+
+                // Make it so.
+                startActivity(domainsIntent);
+                break;
+
+            case R.id.settings:
+                // Set the flag to reapply app settings on restart when returning from Settings.
+                reapplyAppSettingsOnRestart = true;
+
+                // Set the flag to reapply the domain settings on restart when returning from Settings.
+                reapplyDomainSettingsOnRestart = true;
+
+                // Launch the settings activity.
+                Intent settingsIntent = new Intent(this, SettingsActivity.class);
+                startActivity(settingsIntent);
+                break;
+
+            case R.id.import_export:
+                // Launch the import/export activity.
+                Intent importExportIntent = new Intent (this, ImportExportActivity.class);
+                startActivity(importExportIntent);
+                break;
+
+            case R.id.logcat:
+                // Launch the logcat activity.
+                Intent logcatIntent = new Intent(this, LogcatActivity.class);
+                startActivity(logcatIntent);
+                break;
+
+            case R.id.guide:
+                // Launch `GuideActivity`.
+                Intent guideIntent = new Intent(this, GuideActivity.class);
+                startActivity(guideIntent);
+                break;
+
+            case R.id.about:
+                // Create an intent to launch the about activity.
+                Intent aboutIntent = new Intent(this, AboutActivity.class);
+
+                // Create a string array for the blocklist versions.
+                String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0],
+                        ultraPrivacy.get(0).get(0)[0]};
+
+                // Add the blocklist versions to the intent.
+                aboutIntent.putExtra("blocklist_versions", blocklistVersions);
+
+                // Make it so.
+                startActivity(aboutIntent);
+                break;
+        }
+
+        // Get a handle for the drawer layout.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+
+        // Close the navigation drawer.
+        drawerLayout.closeDrawer(GravityCompat.START);
+        return true;
+    }
+
+    @Override
+    public void onPostCreate(Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onPostCreate(savedInstanceState);
+
+        // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
+        actionBarDrawerToggle.syncState();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        // Run the default commands.
+        super.onConfigurationChanged(newConfig);
+
+        // Get the status bar pixel size.
+        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
+
+        // Get the resource density.
+        float screenDensity = getResources().getDisplayMetrics().density;
+
+        // Recalculate the drawer header padding.
+        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+
+        // Reload the ad for the free flavor if not in full screen mode.
+        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+        }
+
+        // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
+        // https://code.google.com/p/android/issues/detail?id=20493#c8
+        // ActivityCompat.invalidateOptionsMenu(this);
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
+        // Store the hit test result.
+        final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
+
+        // Create the URL strings.
+        final String imageUrl;
+        final String linkUrl;
+
+        // Get handles for the system managers.
+        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+        FragmentManager fragmentManager = getSupportFragmentManager();
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+        // Remove the lint errors below that the clipboard manager might be null.
+        assert clipboardManager != null;
+
+        // Process the link according to the type.
+        switch (hitTestResult.getType()) {
+            // `SRC_ANCHOR_TYPE` is a link.
+            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
+                // Get the target URL.
+                linkUrl = hitTestResult.getExtra();
+
+                // Set the target URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(linkUrl);
+
+                // Add a Load URL entry.
+                menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Add a new tab.
+                    addTab(null);
+
+                    // Load the URL.
+                    loadUrl(linkUrl);
+                    return false;
+                });
+
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithApp(linkUrl);
+                    return false;
+                });
+
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithBrowser(linkUrl);
+                    return false;
+                });
+
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Save the link URL in a `ClipData`.
+                    ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
+                    return false;
+                });
+
+                // Add a Download URL entry.
+                menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
+                        openUrlWithExternalApp(linkUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the variables for future use by `onRequestPermissionsResult()`.
+                            downloadUrl = linkUrl;
+                            downloadContentDisposition = "none";
+                            downloadContentLength = -1;
+
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download file alert dialog.
+                            DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
+
+                            // Show the download file alert dialog.
+                            downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
+                    return false;
+                });
+
+                // Add a Cancel entry, which by default closes the context menu.
+                menu.add(R.string.cancel);
+                break;
+
+            case WebView.HitTestResult.EMAIL_TYPE:
+                // Get the target URL.
+                linkUrl = hitTestResult.getExtra();
+
+                // Set the target URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(linkUrl);
+
+                // Add a Write Email entry.
+                menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
+                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+
+                    // Parse the url and set it as the data for the `Intent`.
+                    emailIntent.setData(Uri.parse("mailto:" + linkUrl));
+
+                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
+                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                    // Make it so.
+                    startActivity(emailIntent);
+                    return false;
+                });
+
+                // Add a Copy Email Address entry.
+                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
+                    // Save the email address in a `ClipData`.
+                    ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcEmailTypeClipData);
+                    return false;
+                });
+
+                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+                menu.add(R.string.cancel);
+                break;
+
+            // `IMAGE_TYPE` is an image.
+            case WebView.HitTestResult.IMAGE_TYPE:
+                // Get the image URL.
+                imageUrl = hitTestResult.getExtra();
+
+                // Set the image URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(imageUrl);
+
+                // Add a View Image entry.
+                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+                    loadUrl(imageUrl);
+                    return false;
+                });
+
+                // Add a Download Image entry.
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
+
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
+                    return false;
+                });
+
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                    // Save the image URL in a `ClipData`.
+                    ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcImageTypeClipData);
+                    return false;
+                });
+
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithApp(imageUrl);
+                    return false;
+                });
+
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithBrowser(imageUrl);
+                    return false;
+                });
+
+                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+                menu.add(R.string.cancel);
+                break;
+
+
+            // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
+            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
+                // Get the image URL.
+                imageUrl = hitTestResult.getExtra();
+
+                // Set the image URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(imageUrl);
+
+                // Add a `View Image` entry.
+                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+                    loadUrl(imageUrl);
+                    return false;
+                });
+
+                // Add a `Download Image` entry.
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
+
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
+                    return false;
+                });
+
+                // Add a `Copy URL` entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                    // Save the image URL in a `ClipData`.
+                    ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
+                    return false;
+                });
+
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithApp(imageUrl);
+                    return false;
+                });
+
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithBrowser(imageUrl);
+                    return false;
+                });
+
+                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+                menu.add(R.string.cancel);
+                break;
         }
+    }
 
-        // Enable Clear DOM Storage if there is any.
-        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
+    @Override
+    public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
+        // Get a handle for the bookmarks list view.
+        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
 
-        // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
-        if (Build.VERSION.SDK_INT < 26) {
-            WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
-            clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
-        } else {
-            // Disable clear form data because it is not supported on current version of Android.
-            clearFormDataMenuItem.setEnabled(false);
-        }
+        // Get the views from the dialog fragment.
+        EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
+        EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
 
-        // Enable Clear Data if any of the submenu items are enabled.
-        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+        // Extract the strings from the edit texts.
+        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
 
-        // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
-        fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
+        // Create a favorite icon byte array output stream.
+        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-        // Initialize the display names for the blocklists with the number of blocked requests.
-        blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
-        easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
-        easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
-        fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
-        fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
-        ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
-        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
+        // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
 
-        // Get the current user agent.
-        String currentUserAgent = mainWebView.getSettings().getUserAgentString();
+        // Convert the favorite icon byte array stream to a byte array.
+        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
 
-        // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
-        if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
-            menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
-        } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
-            menu.findItem(R.id.user_agent_webview_default).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
-            menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
-            menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
-            menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
-            menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
-            menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
-            menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
-            menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
-            menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
-            menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
-            menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
-        } else {  // Custom user agent.
-            menu.findItem(R.id.user_agent_custom).setChecked(true);
-        }
+        // Display the new bookmark below the current items in the (0 indexed) list.
+        int newBookmarkDisplayOrder = bookmarksListView.getCount();
 
-        // Initialize font size variables.
-        int fontSize = mainWebView.getSettings().getTextZoom();
-        String fontSizeTitle;
-        MenuItem selectedFontSizeMenuItem;
+        // Create the bookmark.
+        bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
 
-        // Prepare the font size title and current size menu item.
-        switch (fontSize) {
-            case 25:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
-                break;
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-            case 50:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
-                break;
+        // Update the list view.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
 
-            case 75:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
-                break;
+        // Scroll to the new bookmark.
+        bookmarksListView.setSelection(newBookmarkDisplayOrder);
+    }
 
-            case 100:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
-                break;
+    @Override
+    public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
+        // Get a handle for the bookmarks list view.
+        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
 
-            case 125:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
-                break;
+        // Get handles for the views in the dialog fragment.
+        EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
+        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
 
-            case 150:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
-                break;
+        // Get new folder name string.
+        String folderNameString = createFolderNameEditText.getText().toString();
 
-            case 175:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
-                break;
+        // Create a folder icon bitmap.
+        Bitmap folderIconBitmap;
 
-            case 200:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
-                break;
+        // Set the folder icon bitmap according to the dialog.
+        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
+            // Get the default folder icon drawable.
+            Drawable folderIconDrawable = folderIconImageView.getDrawable();
 
-            default:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
-                break;
+            // Convert the folder icon drawable to a bitmap drawable.
+            BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+
+            // Convert the folder icon bitmap drawable to a bitmap.
+            folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+        } else {  // Use the WebView favorite icon.
+            // Copy the favorite icon bitmap to the folder icon bitmap.
+            folderIconBitmap = favoriteIconBitmap;
         }
 
-        // Set the font size title and select the current size menu item.
-        fontSizeMenuItem.setTitle(fontSizeTitle);
-        selectedFontSizeMenuItem.setChecked(true);
+        // Create a folder icon byte array output stream.
+        ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-        // Run all the other default commands.
-        super.onPrepareOptionsMenu(menu);
+        // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
 
-        // Display the menu.
-        return true;
+        // Convert the folder icon byte array stream to a byte array.
+        byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+
+        // Move all the bookmarks down one in the display order.
+        for (int i = 0; i < bookmarksListView.getCount(); i++) {
+            int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
+            bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
+        }
+
+        // Create the folder, which will be placed at the top of the `ListView`.
+        bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
+
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+
+        // Scroll to the new folder.
+        bookmarksListView.setSelection(0);
     }
 
     @Override
-    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
-    @SuppressLint("SetJavaScriptEnabled")
-    // removeAllCookies is deprecated, but it is required for API < 21.
-    @SuppressWarnings("deprecation")
-    public boolean onOptionsItemSelected(MenuItem menuItem) {
-        // Get the selected menu item ID.
-        int menuItemId = menuItem.getItemId();
+    public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
+        EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
+        RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
 
-        // Set the commands that relate to the menu entries.
-        switch (menuItemId) {
-            case R.id.toggle_javascript:
-                // Switch the status of javaScriptEnabled.
-                javaScriptEnabled = !javaScriptEnabled;
+        // Store the bookmark strings.
+        String bookmarkNameString = editBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
 
-                // Apply the new JavaScript status.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+        // Update the bookmark.
+        if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
+        } else {  // Update the bookmark using the `WebView` favorite icon.
+            // Create a favorite icon byte array output stream.
+            ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
 
-                // Display a `Snackbar`.
-                if (javaScriptEnabled) {  // JavaScrip is enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
-                } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
-                }
+            // Convert the favorite icon byte array stream to a byte array.
+            byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            //  Update the bookmark and the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
+        }
 
-            case R.id.add_or_edit_domain:
-                if (domainSettingsApplied) {  // Edit the current domain settings.
-                    // Reapply the domain settings on returning to `MainWebViewActivity`.
-                    reapplyDomainSettingsOnRestart = true;
-                    currentDomainName = "";
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-                    // Create an intent to launch the domains activity.
-                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+        // Update the list view.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
 
-                    // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
-                    domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
-                    domainsIntent.putExtra("closeOnBack", true);
+    @Override
+    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
+        RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
+        ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
 
-                    // Make it so.
-                    startActivity(domainsIntent);
-                } else {  // Add a new domain.
-                    // Apply the new domain settings on returning to `MainWebViewActivity`.
-                    reapplyDomainSettingsOnRestart = true;
-                    currentDomainName = "";
+        // Get the new folder name.
+        String newFolderNameString = editFolderNameEditText.getText().toString();
 
-                    // Get the current domain
-                    Uri currentUri = Uri.parse(formattedUrlString);
-                    String currentDomain = currentUri.getHost();
+        // Check if the favorite icon has changed.
+        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
+            // Update the name in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
+        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
+            // Create the new folder icon Bitmap.
+            Bitmap folderIconBitmap;
 
-                    // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-                    DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+            // Populate the new folder icon bitmap.
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon drawable.
+                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
 
-                    // Create the domain and store the database ID.
-                    int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
+                // Convert the folder icon drawable to a bitmap drawable.
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-                    // Create an intent to launch the domains activity.
-                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+                // Convert the folder icon bitmap drawable to a bitmap.
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                // Copy the favorite icon bitmap to the folder icon bitmap.
+                folderIconBitmap = favoriteIconBitmap;
+            }
 
-                    // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
-                    domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
-                    domainsIntent.putExtra("closeOnBack", true);
+            // Create a folder icon byte array output stream.
+            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                    // Make it so.
-                    startActivity(domainsIntent);
-                }
-                return true;
+            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
 
-            case R.id.toggle_first_party_cookies:
-                // Switch the status of firstPartyCookiesEnabled.
-                firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
+            // Convert the folder icon byte array stream to a byte array.
+            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
 
-                // Update the menu checkbox.
-                menuItem.setChecked(firstPartyCookiesEnabled);
+            // Update the folder icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
+        } else {  // The folder icon and the name have changed.
+            // Get the new folder icon `Bitmap`.
+            Bitmap folderIconBitmap;
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon drawable.
+                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
 
-                // Apply the new cookie status.
-                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
+                // Convert the folder icon drawable to a bitmap drawable.
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+                // Convert the folder icon bitmap drawable to a bitmap.
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                // Copy the favorite icon bitmap to the folder icon bitmap.
+                folderIconBitmap = favoriteIconBitmap;
+            }
 
-                // Display a `Snackbar`.
-                if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (javaScriptEnabled) {  // JavaScript is still enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
-                } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
-                }
+            // Create a folder icon byte array output stream.
+            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
 
-            case R.id.toggle_third_party_cookies:
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Switch the status of thirdPartyCookiesEnabled.
-                    thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
+            // Convert the folder icon byte array stream to a byte array.
+            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
 
-                    // Update the menu checkbox.
-                    menuItem.setChecked(thirdPartyCookiesEnabled);
+            // Update the folder name and icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
+        }
 
-                    // Apply the new cookie status.
-                    cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-                    // Display a `Snackbar`.
-                    if (thirdPartyCookiesEnabled) {
-                        Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                    } else {
-                        Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
-                    }
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
 
-                    // Reload the WebView.
-                    mainWebView.reload();
-                } // Else do nothing because SDK < 21.
-                return true;
+    @Override
+    public void onCloseDownloadLocationPermissionDialog(int downloadType) {
+        switch (downloadType) {
+            case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
+                // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                break;
 
-            case R.id.toggle_dom_storage:
-                // Switch the status of domStorageEnabled.
-                domStorageEnabled = !domStorageEnabled;
+            case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
+                // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                break;
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        // Get a handle for the fragment manager.
+        FragmentManager fragmentManager = getSupportFragmentManager();
+
+        switch (requestCode) {
+            case DOWNLOAD_FILE_REQUEST_CODE:
+                // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
+                DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(domStorageEnabled);
+                // On API 23, displaying the fragment must be delayed or the app will crash.
+                if (Build.VERSION.SDK_INT == 23) {
+                    new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
+                } else {
+                    downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
+                }
 
-                // Apply the new DOM Storage status.
-                mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
+                // Reset the download variables.
+                downloadUrl = "";
+                downloadContentDisposition = "";
+                downloadContentLength = 0;
+                break;
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+            case DOWNLOAD_IMAGE_REQUEST_CODE:
+                // Show the download image alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
+                DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
 
-                // Display a `Snackbar`.
-                if (domStorageEnabled) {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
+                // On API 23, displaying the fragment must be delayed or the app will crash.
+                if (Build.VERSION.SDK_INT == 23) {
+                    new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
                 } else {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
+                    downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
                 }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
-
-            // Form data can be removed once the minimum API >= 26.
-            case R.id.toggle_save_form_data:
-                // Switch the status of saveFormDataEnabled.
-                saveFormDataEnabled = !saveFormDataEnabled;
+                // Reset the image URL variable.
+                downloadImageUrl = "";
+                break;
+        }
+    }
 
-                // Update the menu checkbox.
-                menuItem.setChecked(saveFormDataEnabled);
+    @Override
+    public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
+        // Download the image if it has an HTTP or HTTPS URI.
+        if (imageUrl.startsWith("http")) {
+            // Get a handle for the system `DOWNLOAD_SERVICE`.
+            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
 
-                // Apply the new form data status.
-                mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+            // Parse `imageUrl`.
+            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
 
-                // Display a `Snackbar`.
-                if (saveFormDataEnabled) {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
-                } else {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
-                }
+            // Get a handle for the cookie manager.
+            CookieManager cookieManager = CookieManager.getInstance();
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+            // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
+            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+            if (cookieManager.acceptCookie()) {
+                // Get the cookies for `imageUrl`.
+                String cookies = cookieManager.getCookie(imageUrl);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
+                downloadRequest.addRequestHeader("Cookie", cookies);
+            }
 
-            case R.id.clear_cookies:
-                Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                switch (event) {
-                                    // The user pushed the `Undo` button.
-                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
-                                        // Do nothing.
-                                        break;
-
-                                    // The `Snackbar` was dismissed without the `Undo` button being pushed.
-                                    default:
-                                        // `cookieManager.removeAllCookie()` varies by SDK.
-                                        if (Build.VERSION.SDK_INT < 21) {
-                                            cookieManager.removeAllCookie();
-                                        } else {
-                                            // `null` indicates no callback.
-                                            cookieManager.removeAllCookies(null);
-                                        }
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+            // Get the file name from the dialog fragment.
+            EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
+            String imageName = downloadImageNameEditText.getText().toString();
 
-            case R.id.clear_dom_storage:
-                Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                switch (event) {
-                                    // The user pushed the `Undo` button.
-                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
-                                        // Do nothing.
-                                        break;
-
-                                    // The `Snackbar` was dismissed without the `Undo` button being pushed.
-                                    default:
-                                        // Delete the DOM Storage.
-                                        WebStorage webStorage = WebStorage.getInstance();
-                                        webStorage.deleteAllData();
-
-                                        // Initialize a handler to manually delete the DOM storage files and directories.
-                                        Handler deleteDomStorageHandler = new Handler();
-
-                                        // Setup a runnable to manually delete the DOM storage files and directories.
-                                        Runnable deleteDomStorageRunnable = () -> {
-                                            try {
-                                                // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
-                                                privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
-
-                                                // Multiple commands must be used because `Runtime.exec()` does not like `*`.
-                                                privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
-                                                privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
-                                                privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
-                                                privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
-                                            } catch (IOException e) {
-                                                // Do nothing if an error is thrown.
-                                            }
-                                        };
-
-                                        // Manually delete the DOM storage files after 200 milliseconds.
-                                        deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+            // Specify the download location.
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
+                // Download to the public download directory.
+                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
+            } else {  // External write permission denied.
+                // Download to the app's external download directory.
+                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
+            }
 
-            // Form data can be remove once the minimum API >= 26.
-            case R.id.clear_form_data:
-                Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                switch (event) {
-                                    // The user pushed the `Undo` button.
-                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
-                                        // Do nothing.
-                                        break;
-
-                                    // The `Snackbar` was dismissed without the `Undo` button being pushed.
-                                    default:
-                                        // Delete the form data.
-                                        WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
-                                        mainWebViewDatabase.clearFormData();
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+            // Allow `MediaScanner` to index the download if it is a media file.
+            downloadRequest.allowScanningByMediaScanner();
 
-            case R.id.easylist:
-                // Toggle the EasyList status.
-                easyListEnabled = !easyListEnabled;
+            // Add the URL as the description for the download.
+            downloadRequest.setDescription(imageUrl);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(easyListEnabled);
+            // Show the download notification after the download is completed.
+            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
 
-            case R.id.easyprivacy:
-                // Toggle the EasyPrivacy status.
-                easyPrivacyEnabled = !easyPrivacyEnabled;
+            // Initiate the download.
+            downloadManager.enqueue(downloadRequest);
+        } else {  // The image is not an HTTP or HTTPS URI.
+            Snackbar.make(currentWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
+        }
+    }
 
-                // Update the menu checkbox.
-                menuItem.setChecked(easyPrivacyEnabled);
+    @Override
+    public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
+        // Download the file if it has an HTTP or HTTPS URI.
+        if (downloadUrl.startsWith("http")) {
+            // Get a handle for the system `DOWNLOAD_SERVICE`.
+            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+            // Parse `downloadUrl`.
+            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
 
-            case R.id.fanboys_annoyance_list:
-                // Toggle Fanboy's Annoyance List status.
-                fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
+            // Get a handle for the cookie manager.
+            CookieManager cookieManager = CookieManager.getInstance();
 
-                // Update the menu checkbox.
-                menuItem.setChecked(fanboysAnnoyanceListEnabled);
+            // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
+            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+            if (cookieManager.acceptCookie()) {
+                // Get the cookies for `downloadUrl`.
+                String cookies = cookieManager.getCookie(downloadUrl);
 
-                // Update the staus of Fanboy's Social Blocking List.
-                MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
-                fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
+                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
+                downloadRequest.addRequestHeader("Cookie", cookies);
+            }
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+            // Get the file name from the dialog fragment.
+            EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
+            String fileName = downloadFileNameEditText.getText().toString();
 
-            case R.id.fanboys_social_blocking_list:
-                // Toggle Fanboy's Social Blocking List status.
-                fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
+            // Specify the download location.
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
+                // Download to the public download directory.
+                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
+            } else {  // External write permission denied.
+                // Download to the app's external download directory.
+                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
+            }
 
-                // Update the menu checkbox.
-                menuItem.setChecked(fanboysSocialBlockingListEnabled);
+            // Allow `MediaScanner` to index the download if it is a media file.
+            downloadRequest.allowScanningByMediaScanner();
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+            // Add the URL as the description for the download.
+            downloadRequest.setDescription(downloadUrl);
 
-            case R.id.ultraprivacy:
-                // Toggle the UltraPrivacy status.
-                ultraPrivacyEnabled = !ultraPrivacyEnabled;
+            // Show the download notification after the download is completed.
+            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(ultraPrivacyEnabled);
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+            // Initiate the download.
+            downloadManager.enqueue(downloadRequest);
+        } else {  // The download is not an HTTP or HTTPS URI.
+            Snackbar.make(currentWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
+        }
+    }
 
-            case R.id.block_all_third_party_requests:
-                //Toggle the third-party requests blocker status.
-                blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
+    // Override `onBackPressed` to handle the navigation drawer and and the WebView.
+    @Override
+    public void onBackPressed() {
+        // Get a handle for the drawer layout.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(blockAllThirdPartyRequests);
+        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
+            // Close the navigation drawer.
+            drawerLayout.closeDrawer(GravityCompat.START);
+        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
+            if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
+                // close the bookmarks drawer.
+                drawerLayout.closeDrawer(GravityCompat.END);
+            } else {  // A subfolder is displayed.
+                // Place the former parent folder in `currentFolder`.
+                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+                // Load the new folder.
+                loadBookmarksFolder();
+            }
+        } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
+            // Reset the current domain name so that navigation works if third-party requests are blocked.
+            currentWebView.resetCurrentDomainName();
 
-            case R.id.user_agent_privacy_browser:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
+            // Set navigating history so that the domain settings are applied when the new URL is loaded.
+            currentWebView.setNavigatingHistory(true);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Go back.
+            currentWebView.goBack();
+        } else {  // There is nothing else to do.
+            // Load a blank website.
+            loadUrl("");
+        }
+    }
 
-            case R.id.user_agent_webview_default:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString("");
+    // Process the results of an upload file chooser.  Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // File uploads only work on API >= 21.
+        if (Build.VERSION.SDK_INT >= 21) {
+            // Pass the file to the WebView.
+            fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+        }
+    }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+    private void loadUrlFromTextBox() {
+        // Get a handle for the URL edit text.
+        EditText urlEditText = findViewById(R.id.url_edittext);
 
-            case R.id.user_agent_firefox_on_android:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
+        // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
+        String unformattedUrlString = urlEditText.getText().toString().trim();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Initialize the formatted URL string.
+        String url = "";
 
-            case R.id.user_agent_chrome_on_android:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
+        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
+        if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
+            // Load the entire content URL.
+            url = unformattedUrlString;
+        } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
+                unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
+            // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
+            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
+                unformattedUrlString = "https://" + unformattedUrlString;
+            }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Initialize `unformattedUrl`.
+            URL unformattedUrl = null;
 
-            case R.id.user_agent_safari_on_ios:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
+            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
+            try {
+                unformattedUrl = new URL(unformattedUrlString);
+            } catch (MalformedURLException e) {
+                e.printStackTrace();
+            }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
+            String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
+            String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
+            String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
+            String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
+            String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
 
-            case R.id.user_agent_firefox_on_linux:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
+            // Build the URI.
+            Uri.Builder uri = new Uri.Builder();
+            uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Decode the URI as a UTF-8 string in.
+            try {
+                url = URLDecoder.decode(uri.build().toString(), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                // Do nothing.  The formatted URL string will remain blank.
+            }
+        } else if (!unformattedUrlString.isEmpty()){  // This is not a URL, but rather a search string.
+            // Create an encoded URL String.
+            String encodedUrlString;
 
-            case R.id.user_agent_chromium_on_linux:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
+            // Sanitize the search input.
+            try {
+                encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
+            }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Add the base search URL.
+            url = searchURL + encodedUrlString;
+        }
 
-            case R.id.user_agent_firefox_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
+        // Clear the focus from the URL edit text.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
+        urlEditText.clearFocus();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Make it so.
+        loadUrl(url);
+    }
 
-            case R.id.user_agent_chrome_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
+    private void loadUrl(String url) {
+        // Apply the domain settings.
+        applyDomainSettings(currentWebView, url, true, false);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Load the URL.
+        currentWebView.loadUrl(url, customHeaders);
+    }
 
-            case R.id.user_agent_edge_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
+    public void findPreviousOnPage(View view) {
+        // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
+        currentWebView.findNext(false);
+    }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+    public void findNextOnPage(View view) {
+        // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
+        currentWebView.findNext(true);
+    }
 
-            case R.id.user_agent_internet_explorer_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
+    public void closeFindOnPage(View view) {
+        // Get a handle for the views.
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+        EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Delete the contents of `find_on_page_edittext`.
+        findOnPageEditText.setText(null);
 
-            case R.id.user_agent_safari_on_macos:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
+        // Clear the highlighted phrases.
+        currentWebView.clearMatches();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Hide the find on page linear layout.
+        findOnPageLinearLayout.setVisibility(View.GONE);
 
-            case R.id.user_agent_custom:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+        // Show the toolbar.
+        toolbar.setVisibility(View.VISIBLE);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Get a handle for the input method manager.
+        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-            case R.id.font_size_twenty_five_percent:
-                mainWebView.getSettings().setTextZoom(25);
-                return true;
+        // Remove the lint warning below that the input method manager might be null.
+        assert inputMethodManager != null;
 
-            case R.id.font_size_fifty_percent:
-                mainWebView.getSettings().setTextZoom(50);
-                return true;
+        // Hide the keyboard.
+        inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+    }
 
-            case R.id.font_size_seventy_five_percent:
-                mainWebView.getSettings().setTextZoom(75);
-                return true;
+    private void applyAppSettings() {
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            case R.id.font_size_one_hundred_percent:
-                mainWebView.getSettings().setTextZoom(100);
-                return true;
+        // Store the values from the shared preferences in variables.
+        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
+        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
+        proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
+        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+        hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
 
-            case R.id.font_size_one_hundred_twenty_five_percent:
-                mainWebView.getSettings().setTextZoom(125);
-                return true;
+        // Get handles for the views that need to be modified.
+        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+        ActionBar actionBar = getSupportActionBar();
+        LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
 
-            case R.id.font_size_one_hundred_fifty_percent:
-                mainWebView.getSettings().setTextZoom(150);
-                return true;
+        // Remove the incorrect lint warning below that the action bar might be null.
+        assert actionBar != null;
 
-            case R.id.font_size_one_hundred_seventy_five_percent:
-                mainWebView.getSettings().setTextZoom(175);
-                return true;
+        // Apply the proxy through Orbot settings.
+        applyProxyThroughOrbot(false);
 
-            case R.id.font_size_two_hundred_percent:
-                mainWebView.getSettings().setTextZoom(200);
-                return true;
+        // Set Do Not Track status.
+        if (doNotTrackEnabled) {
+            customHeaders.put("DNT", "1");
+        } else {
+            customHeaders.remove("DNT");
+        }
 
-            case R.id.swipe_to_refresh:
-                // Toggle swipe to refresh.
-                swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
-                return true;
+        // Set the app bar scrolling for each WebView.
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            case R.id.display_images:
-                if (mainWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
-                    mainWebView.getSettings().setLoadsImagesAutomatically(false);
-                    mainWebView.reload();
-                } else {  // Images are not currently loaded automatically.
-                    mainWebView.getSettings().setLoadsImagesAutomatically(true);
-                }
-                return true;
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
 
-            case R.id.night_mode:
-                // Toggle night mode.
-                nightMode = !nightMode;
+            // Only modify the WebViews if they exist.
+            if (fragmentView != null) {
+                // Get the nested scroll WebView from the tab fragment.
+                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
-                if (nightMode) {  // Night mode is enabled.  Enable JavaScript.
-                    // Update the global variable.
-                    javaScriptEnabled = true;
-                } else if (domainSettingsApplied) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
-                    // Get the JavaScript preference that was stored the last time domain settings were loaded.
-                    javaScriptEnabled = domainSettingsJavaScriptEnabled;
-                } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
-                    // Get a handle for the shared preference.
-                    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+                // Set the app bar scrolling.
+                nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
+            }
+        }
 
-                    // Get the JavaScript preference.
-                    javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
-                }
+        // Update the full screen browsing mode settings.
+        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+            // Update the visibility of the app bar, which might have changed in the settings.
+            if (hideAppBar) {
+                // Hide the tab linear layout.
+                tabsLinearLayout.setVisibility(View.GONE);
 
-                // Apply the JavaScript setting to the WebView.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+                // Hide the action bar.
+                actionBar.hide();
+            } else {
+                // Show the tab linear layout.
+                tabsLinearLayout.setVisibility(View.VISIBLE);
 
-                // Update the privacy icons.
-                updatePrivacyIcons(false);
+                // Show the action bar.
+                actionBar.show();
+            }
 
-                // Reload the website.
-                mainWebView.reload();
-                return true;
+            // Hide the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                AdHelper.hideAd(findViewById(R.id.adview));
+            }
 
-            case R.id.print:
-                // Get a `PrintManager` instance.
-                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-                // Convert `mainWebView` to `printDocumentAdapter`.
-                PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else {  // Privacy Browser is not in full screen browsing mode.
+            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
+            inFullScreenBrowsingMode = false;
 
-                // Remove the lint error below that `printManager` might be `null`.
-                assert printManager != null;
+            // Show the tab linear layout.
+            tabsLinearLayout.setVisibility(View.VISIBLE);
 
-                // Print the document.  The print attributes are `null`.
-                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
-                return true;
+            // Show the action bar.
+            actionBar.show();
 
-            case R.id.view_source:
-                // Launch the View Source activity.
-                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
-                startActivity(viewSourceIntent);
-                return true;
+            // Show the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
+                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
+            }
 
-            case R.id.proxy_through_orbot:
-                // Toggle the proxy through Orbot variable.
-                proxyThroughOrbot = !proxyThroughOrbot;
+            // Remove the `SYSTEM_UI` flags from the root frame layout.
+            rootFrameLayout.setSystemUiVisibility(0);
 
-                // Apply the proxy through Orbot settings.
-                applyProxyThroughOrbot(true);
-                return true;
+            // Add the translucent status flag.
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        }
+    }
 
-            case R.id.share:
-                // Setup the share string.
-                String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
 
-                // Create the share intent.
-                Intent shareIntent = new Intent(Intent.ACTION_SEND);
-                shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
-                shareIntent.setType("text/plain");
+    // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
+    @SuppressLint("SetJavaScriptEnabled")
+    private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
+        // Store a copy of the current user agent to track changes for the return boolean.
+        String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
 
-                // Make it so.
-                startActivity(Intent.createChooser(shareIntent, "Share URL"));
-                return true;
+        // Parse the URL into a URI.
+        Uri uri = Uri.parse(url);
 
-            case R.id.find_on_page:
-                // Hide the URL app bar.
-                supportAppBar.setVisibility(View.GONE);
+        // Extract the domain from `uri`.
+        String newHostName = uri.getHost();
 
-                // Show the Find on Page `RelativeLayout`.
-                findOnPageLinearLayout.setVisibility(View.VISIBLE);
+        // Strings don't like to be null.
+        if (newHostName == null) {
+            newHostName = "";
+        }
 
-                // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
-                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
-                findOnPageEditText.postDelayed(() -> {
-                    // Set the focus on `findOnPageEditText`.
-                    findOnPageEditText.requestFocus();
+        // Only apply the domain settings if a new domain is being loaded.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
+        if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName)) {
+            // Set the new host name as the current domain name.
+            nestedScrollWebView.setCurrentDomainName(newHostName);
 
-                    // Display the keyboard.  `0` sets no input flags.
-                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
-                }, 200);
-                return true;
+            // Reset the ignoring of pinned domain information.
+            nestedScrollWebView.setIgnorePinnedDomainInformation(false);
 
-            case R.id.add_to_homescreen:
-                // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
-                AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
-                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+            // Clear any pinned SSL certificate or IP addresses.
+            nestedScrollWebView.clearPinnedSslCertificate();
+            nestedScrollWebView.clearPinnedIpAddresses();
 
-                //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
-                return true;
+            // Reset the favorite icon if specified.
+            if (resetTab) {
+                // Initialize the favorite icon.
+                nestedScrollWebView.initializeFavoriteIcon();
 
-            case R.id.refresh:
-                if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
-                    // Reload the WebView.
-                    mainWebView.reload();
-                } else {  // The stop button was pushed.
-                    // Stop the loading of the WebView.
-                    mainWebView.stopLoading();
-                }
-                return true;
+                // Get the current page position.
+                int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
 
-            case R.id.ad_consent:
-                // Display the ad consent dialog.
-                DialogFragment adConsentDialogFragment = new AdConsentDialog();
-                adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
-                return true;
+                // Get a handle for the tab layout.
+                TabLayout tabLayout = findViewById(R.id.tablayout);
 
-            default:
-                // Don't consume the event.
-                return super.onOptionsItemSelected(menuItem);
-        }
-    }
+                // Get the corresponding tab.
+                TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
 
-    // removeAllCookies is deprecated, but it is required for API < 21.
-    @SuppressWarnings("deprecation")
-    @Override
-    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
-        int menuItemId = menuItem.getItemId();
+                // Remove the warning below that the tab might be null.
+                assert tab != null;
 
-        switch (menuItemId) {
-            case R.id.home:
-                loadUrl(homepage);
-                break;
+                // Get the tab custom view.
+                View tabCustomView = tab.getCustomView();
 
-            case R.id.back:
-                if (mainWebView.canGoBack()) {
-                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-                    formattedUrlString = "";
+                // Remove the warning below that the tab custom view might be null.
+                assert tabCustomView != null;
 
-                    // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-                    navigatingHistory = true;
+                // Get the tab views.
+                ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
+                TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
 
-                    // Load the previous website in the history.
-                    mainWebView.goBack();
-                }
-                break;
+                // Set the default favorite icon as the favorite icon for this tab.
+                tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
 
-            case R.id.forward:
-                if (mainWebView.canGoForward()) {
-                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-                    formattedUrlString = "";
+                // Set the loading title text.
+                tabTitleTextView.setText(R.string.loading);
+            }
 
-                    // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-                    navigatingHistory = true;
+            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
 
-                    // Load the next website in the history.
-                    mainWebView.goForward();
-                }
-                break;
+            // Get a full cursor from `domainsDatabaseHelper`.
+            Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
 
-            case R.id.history:
-                // Get the `WebBackForwardList`.
-                WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
+            // Initialize `domainSettingsSet`.
+            Set<String> domainSettingsSet = new HashSet<>();
 
-                // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`.  `this` is the `Context`.
-                AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
-                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
-                break;
+            // Get the domain name column index.
+            int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
 
-            case R.id.requests:
-                // Launch the requests activity.
-                Intent requestsIntent = new Intent(this, RequestsActivity.class);
-                startActivity(requestsIntent);
-                break;
+            // Populate `domainSettingsSet`.
+            for (int i = 0; i < domainNameCursor.getCount(); i++) {
+                // Move `domainsCursor` to the current row.
+                domainNameCursor.moveToPosition(i);
 
-            case R.id.downloads:
-                // Launch the system Download Manager.
-                Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+                // Store the domain name in `domainSettingsSet`.
+                domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
+            }
 
-                // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
-                downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            // Close `domainNameCursor.
+            domainNameCursor.close();
 
-                startActivity(downloadManagerIntent);
-                break;
+            // Initialize the domain name in database variable.
+            String domainNameInDatabase = null;
 
-            case R.id.domains:
-                // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
-                reapplyDomainSettingsOnRestart = true;
-                currentDomainName = "";
+            // Check the hostname against the domain settings set.
+            if (domainSettingsSet.contains(newHostName)) {  // The hostname is contained in the domain settings set.
+                // Record the domain name in the database.
+                domainNameInDatabase = newHostName;
 
-                // Launch the domains activity.
-                Intent domainsIntent = new Intent(this, DomainsActivity.class);
-                startActivity(domainsIntent);
-                break;
+                // Set the domain settings applied tracker to true.
+                nestedScrollWebView.setDomainSettingsApplied(true);
+            } else {  // The hostname is not contained in the domain settings set.
+                // Set the domain settings applied tracker to false.
+                nestedScrollWebView.setDomainSettingsApplied(false);
+            }
 
-            case R.id.settings:
-                // Set the flag to reapply app settings on restart when returning from Settings.
-                reapplyAppSettingsOnRestart = true;
+            // Check all the subdomains of the host name against wildcard domains in the domain cursor.
+            while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+                if (domainSettingsSet.contains("*." + newHostName)) {  // Check the host name prepended by `*.`.
+                    // Set the domain settings applied tracker to true.
+                    nestedScrollWebView.setDomainSettingsApplied(true);
 
-                // Set the flag to reapply the domain settings on restart when returning from Settings.
-                reapplyDomainSettingsOnRestart = true;
-                currentDomainName = "";
+                    // Store the applied domain names as it appears in the database.
+                    domainNameInDatabase = "*." + newHostName;
+                }
 
-                // Launch the settings activity.
-                Intent settingsIntent = new Intent(this, SettingsActivity.class);
-                startActivity(settingsIntent);
-                break;
+                // Strip out the lowest subdomain of of the host name.
+                newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
+            }
 
-            case R.id.import_export:
-                // Launch the import/export activity.
-                Intent importExportIntent = new Intent (this, ImportExportActivity.class);
-                startActivity(importExportIntent);
-                break;
 
-            case R.id.guide:
-                // Launch `GuideActivity`.
-                Intent guideIntent = new Intent(this, GuideActivity.class);
-                startActivity(guideIntent);
-                break;
+            // Get a handle for the shared preferences.
+            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            case R.id.about:
-                // Launch `AboutActivity`.
-                Intent aboutIntent = new Intent(this, AboutActivity.class);
-                startActivity(aboutIntent);
-                break;
+            // Store the general preference information.
+            String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
+            String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
+            boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
+            boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
+            boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-            case R.id.clearAndExit:
-                // Close the bookmarks cursor and database.
-                bookmarksCursor.close();
-                bookmarksDatabaseHelper.close();
+            // Get a handle for the cookie manager.
+            CookieManager cookieManager = CookieManager.getInstance();
 
-                // Get a handle for `sharedPreferences`.  `this` references the current context.
-                SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+            // Get handles for the views.
+            RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
+            SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
-                boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
+            // Initialize the user agent array adapter and string array.
+            ArrayAdapter<CharSequence> userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
+            String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
 
-                // Clear cookies.
-                if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
-                    // The command to remove cookies changed slightly in API 21.
-                    if (Build.VERSION.SDK_INT >= 21) {
-                        cookieManager.removeAllCookies(null);
-                    } else {
-                        cookieManager.removeAllCookie();
-                    }
+            if (nestedScrollWebView.getDomainSettingsApplied()) {  // The url has custom domain settings.
+                // Get a cursor for the current host and move it to the first position.
+                Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
+                currentDomainSettingsCursor.moveToFirst();
 
-                    // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
-                    try {
-                        // We have to use two commands because `Runtime.exec()` does not like `*`.
-                        privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
-                        privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
-                    } catch (IOException e) {
-                        // Do nothing if an error is thrown.
-                    }
+                // Get the settings from the cursor.
+                nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
+                nestedScrollWebView.setDomainSettingsJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
+                nestedScrollWebView.setAcceptFirstPartyCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
+                boolean domainThirdPartyCookiesEnabled = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
+                nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
+                // Form data can be removed once the minimum API >= 26.
+                boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST,
+                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY,
+                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
+                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
+                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY,
+                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
+                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
+                String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
+                int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
+                int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
+                int nightModeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
+                int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+                boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
+                String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
+                String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
+                String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+                String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+                String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
+                String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+                boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
+                String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
+
+                // Create the pinned SSL date variables.
+                Date pinnedSslStartDate;
+                Date pinnedSslEndDate;
+
+                // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0.
+                if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
+                    pinnedSslStartDate = null;
+                } else {
+                    pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
                 }
 
-                // Clear DOM storage.
-                if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
-                    // Ask `WebStorage` to clear the DOM storage.
-                    WebStorage webStorage = WebStorage.getInstance();
-                    webStorage.deleteAllData();
+                // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0.
+                if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
+                    pinnedSslEndDate = null;
+                } else {
+                    pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                }
 
-                    // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
-                    try {
-                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
-                        privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+                // If there is a pinned SSL certificate, store it in the WebView.
+                if (pinnedSslCertificate) {
+                    nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
+                            pinnedSslStartDate, pinnedSslEndDate);
+                }
 
-                        // Multiple commands must be used because `Runtime.exec()` does not like `*`.
-                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
-                        privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
-                        privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
-                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
-                    } catch (IOException e) {
-                        // Do nothing if an error is thrown.
-                    }
+                // If there is a pinned IP address, store it in the WebView.
+                if (pinnedIpAddresses) {
+                    nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses);
                 }
 
-                // Clear form data if the API < 26.
-                if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
-                    WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
-                    webViewDatabase.clearFormData();
+                // Set night mode according to the night mode int.
+                switch (nightModeInt) {
+                    case DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT:
+                        // Set night mode according to the current default.
+                        nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
+                        break;
 
-                    // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
-                    try {
-                        // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise.
-                        privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
-                        privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
-                    } catch (IOException e) {
-                        // Do nothing if an error is thrown.
-                    }
+                    case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
+                        // Enable night mode.
+                        nestedScrollWebView.setNightMode(true);
+                        break;
+
+                    case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
+                        // Disable night mode.
+                        nestedScrollWebView.setNightMode(false);
+                        break;
                 }
 
-                // Clear the cache.
-                if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
-                    // `true` includes disk files.
-                    mainWebView.clearCache(true);
+                // Enable JavaScript if night mode is enabled.
+                if (nestedScrollWebView.getNightMode()) {
+                    // Enable JavaScript.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
+                } else {
+                    // Set JavaScript according to the domain settings.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(nestedScrollWebView.getDomainSettingsJavaScriptEnabled());
+                }
 
-                    // Manually delete the cache directories.
-                    try {
-                        // Delete the main cache directory.
-                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
+                // Close the current host domain settings cursor.
+                currentDomainSettingsCursor.close();
 
-                        // Delete the secondary `Service Worker` cache directory.
-                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
-                        privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
-                    } catch (IOException e) {
-                        // Do nothing if an error is thrown.
-                    }
+                // Apply the domain settings.
+                cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
+
+                // Set third-party cookies status if API >= 21.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, domainThirdPartyCookiesEnabled);
                 }
 
-                // Clear SSL certificate preferences.
-                mainWebView.clearSslPreferences();
+                // Apply the form data setting if the API < 26.
+                if (Build.VERSION.SDK_INT < 26) {
+                    nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
+                }
 
-                // Clear the back/forward history.
-                mainWebView.clearHistory();
+                // Apply the font size.
+                if (fontSize == 0) {  // Apply the default font size.
+                    nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+                } else {  // Apply the specified font size.
+                    nestedScrollWebView.getSettings().setTextZoom(fontSize);
+                }
 
-                // Clear `formattedUrlString`.
-                formattedUrlString = null;
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
+                // <https://redmine.stoutner.com/issues/160>
+                if (nestedScrollWebView.getProgress() == 100) {  // A URL is not loading.
+                    // Set the user agent.
+                    if (userAgentName.equals(getString(R.string.system_default_user_agent))) {  // Use the system default user agent.
+                        // Get the array position of the default user agent name.
+                        int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
 
-                // Clear `customHeaders`.
-                customHeaders.clear();
+                        // Set the user agent according to the system default.
+                        switch (defaultUserAgentArrayPosition) {
+                            case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
+                                // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+                                nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
+                                break;
 
-                // Detach all views from `mainWebViewRelativeLayout`.
-                mainWebViewRelativeLayout.removeAllViews();
+                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                                // Set the user agent to `""`, which uses the default value.
+                                nestedScrollWebView.getSettings().setUserAgentString("");
+                                break;
 
-                // Destroy the internal state of `mainWebView`.
-                mainWebView.destroy();
+                            case SETTINGS_CUSTOM_USER_AGENT:
+                                // Set the default custom user agent.
+                                nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+                                break;
 
-                // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
-                // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
-                if (clearEverything) {
-                    try {
-                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
-                    } catch (IOException e) {
-                        // Do nothing if an error is thrown.
-                    }
-                }
+                            default:
+                                // Get the user agent string from the user agent data array
+                                nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
+                        }
+                    } else {  // Set the user agent according to the stored name.
+                        // Get the array position of the user agent name.
+                        int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
 
-                // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    finishAndRemoveTask();
-                } else {
-                    finish();
-                }
+                        switch (userAgentArrayPosition) {
+                            case UNRECOGNIZED_USER_AGENT:  // The user agent name contains a custom user agent.
+                                nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
+                                break;
 
-                // Remove the terminated program from RAM.  The status code is `0`.
-                System.exit(0);
-                break;
-        }
+                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                                // Set the user agent to `""`, which uses the default value.
+                                nestedScrollWebView.getSettings().setUserAgentString("");
+                                break;
 
-        // Close the navigation drawer.
-        drawerLayout.closeDrawer(GravityCompat.START);
-        return true;
-    }
+                            default:
+                                // Get the user agent string from the user agent data array.
+                                nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
+                        }
+                    }
+                }
 
-    @Override
-    public void onPostCreate(Bundle savedInstanceState) {
-        super.onPostCreate(savedInstanceState);
+                // Set swipe to refresh.
+                switch (swipeToRefreshInt) {
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
+                        // Store the swipe to refresh status in the nested scroll WebView.
+                        nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
 
-        // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
-        drawerToggle.syncState();
-    }
+                        // Apply swipe to refresh according to the default.  This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
+                        swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
+                        break;
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
+                        // Store the swipe to refresh status in the nested scroll WebView.
+                        nestedScrollWebView.setSwipeToRefresh(true);
 
-        // Reload the ad for the free flavor if we not in full screen mode.
-        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
-        }
+                        // Enable swipe to refresh.  This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
+                        swipeRefreshLayout.setEnabled(true);
+                        break;
 
-        // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
-        // https://code.google.com/p/android/issues/detail?id=20493#c8
-        // ActivityCompat.invalidateOptionsMenu(this);
-    }
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
+                        // Store the swipe to refresh status in the nested scroll WebView.
+                        nestedScrollWebView.setSwipeToRefresh(false);
 
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
-        // Store the `HitTestResult`.
-        final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
+                        // Disable swipe to refresh.  This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
+                        swipeRefreshLayout.setEnabled(false);
+                }
 
-        // Create strings.
-        final String imageUrl;
-        final String linkUrl;
+                // Set the loading of webpage images.
+                switch (displayWebpageImagesInt) {
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
+                        nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
+                        break;
 
-        // Get a handle for the `ClipboardManager`.
-        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
+                        nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true);
+                        break;
 
-        // Remove the lint errors below that `clipboardManager` might be `null`.
-        assert clipboardManager != null;
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
+                        nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false);
+                        break;
+                }
 
-        switch (hitTestResult.getType()) {
-            // `SRC_ANCHOR_TYPE` is a link.
-            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
-                // Get the target URL.
-                linkUrl = hitTestResult.getExtra();
+                // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
+                if (darkTheme) {
+                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
+                } else {
+                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
+                }
+            } else {  // The new URL does not have custom domain settings.  Load the defaults.
+                // Store the values from the shared preferences.
+                boolean defaultJavaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
+                nestedScrollWebView.setAcceptFirstPartyCookies(sharedPreferences.getBoolean("first_party_cookies", false));
+                boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
+                nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
+                boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, sharedPreferences.getBoolean("easylist", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
+                nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
 
-                // Set the target URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(linkUrl);
+                // Enable JavaScript if night mode is enabled.
+                if (nestedScrollWebView.getNightMode()) {
+                    // Enable JavaScript.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
+                } else {
+                    // Set JavaScript according to the domain settings.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(defaultJavaScriptEnabled);
+                }
 
-                // Add a Load URL entry.
-                menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    loadUrl(linkUrl);
-                    return false;
-                });
+                // Apply the default settings.
+                cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
+                nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
 
-                // Add a Copy URL entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Save the link URL in a `ClipData`.
-                    ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
+                // Apply the form data setting if the API < 26.
+                if (Build.VERSION.SDK_INT < 26) {
+                    nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
+                }
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
-                    return false;
-                });
+                // Store the swipe to refresh status in the nested scroll WebView.
+                nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
 
-                // Add a Download URL entry.
-                menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (downloadWithExternalApp) {  // Download with an external app.
-                        openUrlWithExternalApp(linkUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the variables for future use by `onRequestPermissionsResult()`.
-                            downloadUrl = linkUrl;
-                            downloadContentDisposition = "none";
-                            downloadContentLength = -1;
+                // Apply swipe to refresh according to the default.
+                swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+                // Reset the pinned variables.
+                nestedScrollWebView.setDomainSettingsDatabaseId(-1);
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download file alert dialog.
-                            AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
+                // Set third-party cookies status if API >= 21.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled);
+                }
 
-                            // Show the download file alert dialog.
-                            downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
+                // <https://redmine.stoutner.com/issues/160>
+                if (nestedScrollWebView.getProgress() == 100) {  // A URL is not loading.
+                    // Get the array position of the user agent name.
+                    int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+                    // Set the user agent.
+                    switch (userAgentArrayPosition) {
+                        case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
+                            // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+                            nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
+                            break;
 
-            case WebView.HitTestResult.EMAIL_TYPE:
-                // Get the target URL.
-                linkUrl = hitTestResult.getExtra();
+                        case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                            // Set the user agent to `""`, which uses the default value.
+                            nestedScrollWebView.getSettings().setUserAgentString("");
+                            break;
 
-                // Set the target URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(linkUrl);
+                        case SETTINGS_CUSTOM_USER_AGENT:
+                            // Set the default custom user agent.
+                            nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+                            break;
+
+                        default:
+                            // Get the user agent string from the user agent data array
+                            nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
+                    }
+                }
 
-                // Add a `Write Email` entry.
-                menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
-                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
-                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+                // Set the loading of webpage images.
+                nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
 
-                    // Parse the url and set it as the data for the `Intent`.
-                    emailIntent.setData(Uri.parse("mailto:" + linkUrl));
+                // Set a transparent background on URL edit text.  The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21.
+                urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
+            }
 
-                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
-                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            // Close the domains database helper.
+            domainsDatabaseHelper.close();
 
-                    // Make it so.
-                    startActivity(emailIntent);
-                    return false;
-                });
+            // Update the privacy icons.
+            updatePrivacyIcons(true);
+        }
 
-                // Add a `Copy Email Address` entry.
-                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
-                    // Save the email address in a `ClipData`.
-                    ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
+        // Reload the website if returning from the Domains activity.
+        if (reloadWebsite) {
+            nestedScrollWebView.reload();
+        }
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcEmailTypeClipData);
-                    return false;
-                });
+        // Return the user agent changed status.
+        return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent);
+    }
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+    private void applyProxyThroughOrbot(boolean reloadWebsite) {
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            // `IMAGE_TYPE` is an image.
-            case WebView.HitTestResult.IMAGE_TYPE:
-                // Get the image URL.
-                imageUrl = hitTestResult.getExtra();
+        // Get the search and theme preferences.
+        String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
+        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
+        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
+        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
+        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-                // Set the image URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(imageUrl);
+        // Get a handle for the action bar.  `getSupportActionBar()` must be used until the minimum API >= 21.
+        ActionBar actionBar = getSupportActionBar();
 
-                // Add a `View Image` entry.
-                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
-                    loadUrl(imageUrl);
-                    return false;
-                });
+        // Remove the incorrect lint warning later that the action bar might be null.
+        assert actionBar != null;
 
-                // Add a `Download Image` entry.
-                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (downloadWithExternalApp) {  // Download with an external app.
-                        openUrlWithExternalApp(imageUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the image URL for use by `onRequestPermissionResult()`.
-                            downloadImageUrl = imageUrl;
+        // Set the homepage, search, and proxy options.
+        if (proxyThroughOrbot) {  // Set the Tor options.
+            // Set the search URL.
+            if (torSearchString.equals("Custom URL")) {  // Get the custom URL string.
+                searchURL = torSearchCustomUrlString;
+            } else {  // Use the string from the pre-built list.
+                searchURL = torSearchString;
+            }
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+            // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
+            OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download image alert dialog.
-                            AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+            // Set the `appBar` background to indicate proxying through Orbot is enabled.
+            if (darkTheme) {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
+            } else {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
+            }
 
-                            // Show the download image alert dialog.
-                            downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+            // Check to see if Orbot is ready.
+            if (!orbotStatus.equals("ON")) {  // Orbot is not ready.
+                // Set `waitingForOrbot`.
+                waitingForOrbot = true;
 
-                // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
-                    // Save the image URL in a `ClipData`.
-                    ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+                // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+                currentWebView.getSettings().setUseWideViewPort(false);
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcImageTypeClipData);
-                    return false;
-                });
+                // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
+                currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
+            } else if (reloadWebsite) {  // Orbot is ready and the website should be reloaded.
+                // Reload the website.
+                currentWebView.reload();
+            }
+        } else {  // Set the non-Tor options.
+            // Set the search URL.
+            if (searchString.equals("Custom URL")) {  // Get the custom URL string.
+                searchURL = searchCustomUrlString;
+            } else {  // Use the string from the pre-built list.
+                searchURL = searchString;
+            }
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+            // Reset the proxy to default.  The host is `""` and the port is `"0"`.
+            OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
 
+            // Set the default `appBar` background.
+            if (darkTheme) {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
+            } else {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
+            }
 
-            // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
-            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
-                // Get the image URL.
-                imageUrl = hitTestResult.getExtra();
+            // Reset `waitingForOrbot.
+            waitingForOrbot = false;
 
-                // Set the image URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(imageUrl);
+            // Reload the WebViews if requested.
+            if (reloadWebsite) {
+                // Reload the WebViews.
+                for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                    // Get the WebView tab fragment.
+                    WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-                // Add a `View Image` entry.
-                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
-                    loadUrl(imageUrl);
-                    return false;
-                });
+                    // Get the fragment view.
+                    View fragmentView = webViewTabFragment.getView();
 
-                // Add a `Download Image` entry.
-                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (downloadWithExternalApp) {  // Download with an external app.
-                        openUrlWithExternalApp(imageUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the image URL for use by `onRequestPermissionResult()`.
-                            downloadImageUrl = imageUrl;
+                    // Only reload the WebViews if they exist.
+                    if (fragmentView != null) {
+                        // Get the nested scroll WebView from the tab fragment.
+                        NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+                        // Reload the WebView.
+                        nestedScrollWebView.reload();
+                    }
+                }
+            }
+        }
+    }
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download image alert dialog.
-                            AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+    private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
+        // Only update the privacy icons if the options menu and the current WebView have already been populated.
+        if ((optionsMenu != null) && (currentWebView != null)) {
+            // Get a handle for the shared preferences.
+            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                            // Show the download image alert dialog.
-                            downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+            // Get the theme and screenshot preferences.
+            boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+
+            // Get handles for the menu items.
+            MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript);
+            MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies);
+            MenuItem domStorageMenuItem = optionsMenu.findItem(R.id.toggle_dom_storage);
+            MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
+
+            // Update the privacy icon.
+            if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled.
+                privacyMenuItem.setIcon(R.drawable.javascript_enabled);
+            } else if (currentWebView.getAcceptFirstPartyCookies()) {  // JavaScript is disabled but cookies are enabled.
+                privacyMenuItem.setIcon(R.drawable.warning);
+            } else {  // All the dangerous features are disabled.
+                privacyMenuItem.setIcon(R.drawable.privacy_mode);
+            }
 
-                // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
-                    // Save the image URL in a `ClipData`.
-                    ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+            // Update the first-party cookies icon.
+            if (currentWebView.getAcceptFirstPartyCookies()) {  // First-party cookies are enabled.
+                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
+            } else {  // First-party cookies are disabled.
+                if (darkTheme) {
+                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
+                } else {
+                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
+                }
+            }
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
-                    return false;
-                });
+            // Update the DOM storage icon.
+            if (currentWebView.getSettings().getJavaScriptEnabled() && currentWebView.getSettings().getDomStorageEnabled()) {  // Both JavaScript and DOM storage are enabled.
+                domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
+            } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled but DOM storage is disabled.
+                if (darkTheme) {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
+                } else {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
+                }
+            } else {  // JavaScript is disabled, so DOM storage is ghosted.
+                if (darkTheme) {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
+                } else {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
+                }
+            }
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+            // Update the refresh icon.
+            if (darkTheme) {
+                refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
+            } else {
+                refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
+            }
+
+            // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
+            if (runInvalidateOptionsMenu) {
+                invalidateOptionsMenu();
+            }
         }
     }
 
-    @Override
-    public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
-        // Get the `EditTexts` from the `dialogFragment`.
-        EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
-        EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+    private void openUrlWithExternalApp(String url) {
+        // Create a download intent.  Not specifying the action type will display the maximum number of options.
+        Intent downloadIntent = new Intent();
 
-        // Extract the strings from the `EditTexts`.
-        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
-        String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
+        // Set the URI and the MIME type.  Specifying `text/html` displays a good number of options.
+        downloadIntent.setDataAndType(Uri.parse(url), "text/html");
 
-        // Convert the favoriteIcon Bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
-        favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
-        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+        // Flag the intent to open in a new task.
+        downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-        // Display the new bookmark below the current items in the (0 indexed) list.
-        int newBookmarkDisplayOrder = bookmarksListView.getCount();
+        // Show the chooser.
+        startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
+    }
 
-        // Create the bookmark.
-        bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
+    private void highlightUrlText() {
+        // Get a handle for the URL edit text.
+        EditText urlEditText = findViewById(R.id.url_edittext);
+
+        // Only highlight the URL text if the box is not currently selected.
+        if (!urlEditText.hasFocus()) {
+            // Get the URL string.
+            String urlString = urlEditText.getText().toString();
+
+            // Highlight the URL according to the protocol.
+            if (urlString.startsWith("file://")) {  // This is a file URL.
+                // De-emphasize only the protocol.
+                urlEditText.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else if (urlString.startsWith("content://")) {
+                // De-emphasize only the protocol.
+                urlEditText.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {  // This is a web URL.
+                // Get the index of the `/` immediately after the domain name.
+                int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
+
+                // Create a base URL string.
+                String baseUrl;
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+                // Get the base URL.
+                if (endOfDomainName > 0) {  // There is at least one character after the base URL.
+                    // Get the base URL.
+                    baseUrl = urlString.substring(0, endOfDomainName);
+                } else {  // There are no characters after the base URL.
+                    // Set the base URL to be the entire URL string.
+                    baseUrl = urlString;
+                }
 
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+                // Get the index of the last `.` in the domain.
+                int lastDotIndex = baseUrl.lastIndexOf(".");
 
-        // Scroll to the new bookmark.
-        bookmarksListView.setSelection(newBookmarkDisplayOrder);
-    }
+                // Get the index of the penultimate `.` in the domain.
+                int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
 
-    @Override
-    public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
-        // Get handles for the views in `dialogFragment`.
-        EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
-        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
-        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
+                // Markup the beginning of the URL.
+                if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
+                    urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
 
-        // Get new folder name string.
-        String folderNameString = createFolderNameEditText.getText().toString();
+                    // De-emphasize subdomains.
+                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
+                        urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    }
+                } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
+                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
+                        // De-emphasize the protocol and the additional subdomains.
+                        urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    } else {  // There is only one subdomain in the domain name.
+                        // De-emphasize only the protocol.
+                        urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    }
+                }
 
-        // Get the new folder icon `Bitmap`.
-        Bitmap folderIconBitmap;
-        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
-            // Get the default folder icon and convert it to a `Bitmap`.
-            Drawable folderIconDrawable = folderIconImageView.getDrawable();
-            BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
-            folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-        } else {  // Use the `WebView` favorite icon.
-            folderIconBitmap = favoriteIconBitmap;
+                // De-emphasize the text after the domain name.
+                if (endOfDomainName > 0) {
+                    urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+            }
         }
+    }
 
-        // Convert `folderIconBitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
-        ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
-        folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
-        byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+    private void loadBookmarksFolder() {
+        // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-        // Move all the bookmarks down one in the display order.
-        for (int i = 0; i < bookmarksListView.getCount(); i++) {
-            int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
-            bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
-        }
+        // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
+        bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
+            @Override
+            public View newView(Context context, Cursor cursor, ViewGroup parent) {
+                // Inflate the individual item layout.  `false` does not attach it to the root.
+                return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
+            }
 
-        // Create the folder, which will be placed at the top of the `ListView`.
-        bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
+            @Override
+            public void bindView(View view, Context context, Cursor cursor) {
+                // Get handles for the views.
+                ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
+                TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+                // Get the favorite icon byte array from the cursor.
+                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
 
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+                // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
+                Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
 
-        // Scroll to the new folder.
-        bookmarksListView.setSelection(0);
-    }
+                // Display the bitmap in `bookmarkFavoriteIcon`.
+                bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
 
-    @Override
-    public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
-        // Get the shortcut name.
-        EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
-        String shortcutNameString = shortcutNameEditText.getText().toString();
+                // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
+                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+                bookmarkNameTextView.setText(bookmarkNameString);
 
-        // Convert the favorite icon bitmap to an `Icon`.  `IconCompat` is required until API >= 26.
-        IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
+                // Make the font bold for folders.
+                if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
+                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
+                } else {  // Reset the font to default for normal bookmarks.
+                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
+                }
+            }
+        };
 
-        // Setup the shortcut intent.
-        Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
-        shortcutIntent.setData(Uri.parse(formattedUrlString));
+        // Get a handle for the bookmarks list view.
+        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
 
-        // Create a shortcut info builder.  The shortcut name becomes the shortcut ID.
-        ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
+        // Populate the list view with the adapter.
+        bookmarksListView.setAdapter(bookmarksCursorAdapter);
 
-        // Add the required fields to the shortcut info builder.
-        shortcutInfoBuilder.setIcon(favoriteIcon);
-        shortcutInfoBuilder.setIntent(shortcutIntent);
-        shortcutInfoBuilder.setShortLabel(shortcutNameString);
+        // Get a handle for the bookmarks title text view.
+        TextView bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
 
-        // Request the pin.  `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
-        ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
+        // Set the bookmarks drawer title.
+        if (currentBookmarksFolder.isEmpty()) {
+            bookmarksTitleTextView.setText(R.string.bookmarks);
+        } else {
+            bookmarksTitleTextView.setText(currentBookmarksFolder);
+        }
     }
 
-    @Override
-    public void onCloseDownloadLocationPermissionDialog(int downloadType) {
-        switch (downloadType) {
-            case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
-                // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
-                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                break;
+    private void openWithApp(String url) {
+        // Create the open with intent with `ACTION_VIEW`.
+        Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
 
-            case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
-                // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
-                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                break;
-        }
-    }
+        // Set the URI but not the MIME type.  This should open all available apps.
+        openWithAppIntent.setData(Uri.parse(url));
 
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        switch (requestCode) {
-            case DOWNLOAD_FILE_REQUEST_CODE:
-                // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
-                AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
+        // Flag the intent to open in a new task.
+        openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                // On API 23, displaying the fragment must be delayed or the app will crash.
-                if (Build.VERSION.SDK_INT == 23) {
-                    new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
-                } else {
-                    downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
-                }
+        // Show the chooser.
+        startActivity(openWithAppIntent);
+    }
 
-                // Reset the download variables.
-                downloadUrl = "";
-                downloadContentDisposition = "";
-                downloadContentLength = 0;
-                break;
+    private void openWithBrowser(String url) {
+        // Create the open with intent with `ACTION_VIEW`.
+        Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
 
-            case DOWNLOAD_IMAGE_REQUEST_CODE:
-                // Show the download image alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
-                AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
+        // Set the URI and the MIME type.  `"text/html"` should load browser options.
+        openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
 
-                // On API 23, displaying the fragment must be delayed or the app will crash.
-                if (Build.VERSION.SDK_INT == 23) {
-                    new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
-                } else {
-                    downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
-                }
+        // Flag the intent to open in a new task.
+        openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                // Reset the image URL variable.
-                downloadImageUrl = "";
-                break;
-        }
+        // Show the chooser.
+        startActivity(openWithBrowserIntent);
     }
 
-    @Override
-    public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
-        // Download the image if it has an HTTP or HTTPS URI.
-        if (imageUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+    public void addTab(View view) {
+        // Get a handle for the tab layout and the view pager.
+        TabLayout tabLayout = findViewById(R.id.tablayout);
+        ViewPager webViewPager = findViewById(R.id.webviewpager);
 
-            // Parse `imageUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
+        // Get the new page number.  The page numbers are 0 indexed, so the new page number will match the current count.
+        int newTabNumber = tabLayout.getTabCount();
 
-            // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (firstPartyCookiesEnabled) {
-                // Get the cookies for `imageUrl`.
-                String cookies = cookieManager.getCookie(imageUrl);
+        // Add a new tab.
+        tabLayout.addTab(tabLayout.newTab());
 
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
-            }
+        // Get the new tab.
+        TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber);
 
-            // Get the file name from the dialog fragment.
-            EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
-            String imageName = downloadImageNameEditText.getText().toString();
+        // Remove the lint warning below that the current tab might be null.
+        assert newTab != null;
 
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
-            }
+        // Set a custom view on the new tab.
+        newTab.setCustomView(R.layout.tab_custom_view);
 
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
+        // Add the new WebView page.
+        webViewPagerAdapter.addPage(newTabNumber, webViewPager);
+    }
 
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(imageUrl);
+    private void setCurrentWebView(int pageNumber) {
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+        // Get the theme preference.
+        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
+        // Get handles for the URL views.
+        RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
+        EditText urlEditText = findViewById(R.id.url_edittext);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The image is not an HTTP or HTTPS URI.
-            Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
-        }
-    }
+        //Stop the swipe to refresh indicator if it is running
+        swipeRefreshLayout.setRefreshing(false);
 
-    @Override
-    public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
-        // Download the file if it has an HTTP or HTTPS URI.
-        if (downloadUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+        // Get the WebView tab fragment.
+        WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber);
 
-            // Parse `downloadUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
+        // Get the fragment view.
+        View fragmentView = webViewTabFragment.getView();
 
-            // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (firstPartyCookiesEnabled) {
-                // Get the cookies for `downloadUrl`.
-                String cookies = cookieManager.getCookie(downloadUrl);
+        // Set the current WebView if the fragment view is not null.
+        if (fragmentView != null) {
+            // Store the current WebView.
+            currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
+            // Update the status of swipe to refresh.
+            if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
+                if (Build.VERSION.SDK_INT >= 23) {  // For API >= 23, swipe refresh layout is continuously updated with an on scroll change listener and only enabled if the WebView is scrolled to the top.
+                    // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.
+                    swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                } else {
+                    // Enable the swipe refresh layout.
+                    swipeRefreshLayout.setEnabled(true);
+                }
+            } else {  // Swipe to refresh is disabled.
+                // Disable the swipe refresh layout.
+                swipeRefreshLayout.setEnabled(false);
             }
 
-            // Get the file name from the dialog fragment.
-            EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
-            String fileName = downloadFileNameEditText.getText().toString();
+            // Get a handle for the cookie manager.
+            CookieManager cookieManager = CookieManager.getInstance();
 
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
-            }
+            // Set the first-party cookie status.
+            cookieManager.setAcceptCookie(currentWebView.getAcceptFirstPartyCookies());
 
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
+            // Update the privacy icons.  `true` redraws the icons in the app bar.
+            updatePrivacyIcons(true);
 
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(downloadUrl);
+            // Clear the focus from the URL text box.
+            urlEditText.clearFocus();
 
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+            // Get a handle for the input method manager.
+            InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
+            // Remove the lint warning below that the input method manager might be null.
+            assert inputMethodManager != null;
 
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The download is not an HTTP or HTTPS URI.
-            Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
+            // Hide the soft keyboard.
+            inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+
+            // Display the current URL in the URL text box.
+            urlEditText.setText(currentWebView.getUrl());
+
+            // Highlight the URL text.
+            highlightUrlText();
+
+            // Set the background to indicate the domain settings status.
+            if (currentWebView.getDomainSettingsApplied()) {
+                // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
+                if (darkTheme) {
+                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
+                } else {
+                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
+                }
+            } else {
+                urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
+            }
         }
     }
 
     @Override
-    public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
-        // Get handles for the views from `dialogFragment`.
-        EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
-        EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
-        RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
-
-        // Store the bookmark strings.
-        String bookmarkNameString = editBookmarkNameEditText.getText().toString();
-        String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
-
-        // Update the bookmark.
-        if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
-            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
-        } else {  // Update the bookmark using the `WebView` favorite icon.
-            // Convert the favorite icon to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
-            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
-            byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
+    public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar) {
+        // Get handles for the activity views.
+        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
+        ActionBar actionBar = getSupportActionBar();
+        LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
+        EditText urlEditText = findViewById(R.id.url_edittext);
+        TabLayout tabLayout = findViewById(R.id.tablayout);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
+        // Remove the incorrect lint warning below that the action bar might be null.
+        assert actionBar != null;
+
+        // Get a handle for the activity
+        Activity activity = this;
 
-            //  Update the bookmark and the favorite icon.
-            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
-        }
+        // Get a handle for the input method manager.
+        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+        // Instantiate the blocklist helper.
+        BlockListHelper blockListHelper = new BlockListHelper();
 
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
-    }
+        // Remove the lint warning below that the input method manager might be null.
+        assert inputMethodManager != null;
 
-    @Override
-    public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
-        // Get handles for the views from `dialogFragment`.
-        EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
-        RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
-        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
-        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get the new folder name.
-        String newFolderNameString = editFolderNameEditText.getText().toString();
+        // Get the relevant preferences.
+        boolean downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
 
-        // Check if the favorite icon has changed.
-        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
-            // Update the name in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
-        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
-            // Get the new folder icon `Bitmap`.
-            Bitmap folderIconBitmap;
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon and convert it to a `Bitmap`.
-                Drawable folderIconDrawable = folderIconImageView.getDrawable();
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                folderIconBitmap = favoriteIconBitmap;
-            }
+        // Initialize the favorite icon.
+        nestedScrollWebView.initializeFavoriteIcon();
 
-            // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
-            byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+        // Set the app bar scrolling.
+        nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
 
-            // Update the folder icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
-        } else {  // The folder icon and the name have changed.
-            // Get the new folder icon `Bitmap`.
-            Bitmap folderIconBitmap;
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon and convert it to a `Bitmap`.
-                Drawable folderIconDrawable = folderIconImageView.getDrawable();
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
-            }
+        // Allow pinch to zoom.
+        nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
 
-            // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
-            byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+        // Hide zoom controls.
+        nestedScrollWebView.getSettings().setDisplayZoomControls(false);
 
-            // Update the folder name and icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
+        // Don't allow mixed content (HTTP and HTTPS) on the same website.
+        if (Build.VERSION.SDK_INT >= 21) {
+            nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
         }
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+        // Set the WebView to use a wide viewport.  Otherwise, some web pages will be scrunched and some content will render outside the screen.
+        nestedScrollWebView.getSettings().setUseWideViewPort(true);
 
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
-    }
+        // Set the WebView to load in overview mode (zoomed out to the maximum width).
+        nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
 
-    @Override
-    public void onHttpAuthenticationCancel() {
-        // Cancel the `HttpAuthHandler`.
-        httpAuthHandler.cancel();
-    }
+        // Explicitly disable geolocation.
+        nestedScrollWebView.getSettings().setGeolocationEnabled(false);
 
-    @Override
-    public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
-        // Get handles for the `EditTexts`.
-        EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
-        EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
+        // Create a double-tap gesture detector to toggle full-screen mode.
+        GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
+            // Override `onDoubleTap()`.  All other events are handled using the default settings.
+            @Override
+            public boolean onDoubleTap(MotionEvent event) {
+                if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
+                    // Toggle the full screen browsing mode tracker.
+                    inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
 
-        // Proceed with the HTTP authentication.
-        httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
-    }
+                    // Toggle the full screen browsing mode.
+                    if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
+                        // Hide the app bar if specified.
+                        if (hideAppBar) {
+                            // Hide the tab linear layout.
+                            tabsLinearLayout.setVisibility(View.GONE);
 
-    public void viewSslCertificate(View view) {
-        // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
-        DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
-        viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
-    }
+                            // Hide the action bar.
+                            actionBar.hide();
+                        }
 
-    @Override
-    public void onSslErrorCancel() {
-        sslErrorHandler.cancel();
-    }
+                        // Hide the banner ad in the free flavor.
+                        if (BuildConfig.FLAVOR.contentEquals("free")) {
+                            AdHelper.hideAd(findViewById(R.id.adview));
+                        }
 
-    @Override
-    public void onSslErrorProceed() {
-        sslErrorHandler.proceed();
-    }
+                        // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+                        /* Hide the system bars.
+                         * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                         * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                         * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                         * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                         */
+                        rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                                View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                    } else {  // Switch to normal viewing mode.
+                        // Show the tab linear layout.
+                        tabsLinearLayout.setVisibility(View.VISIBLE);
 
-    @Override
-    public void onSslMismatchBack() {
-        if (mainWebView.canGoBack()) {  // There is a back page in the history.
-            // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-            formattedUrlString = "";
+                        // Show the action bar.
+                        actionBar.show();
 
-            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-            navigatingHistory = true;
+                        // Show the banner ad in the free flavor.
+                        if (BuildConfig.FLAVOR.contentEquals("free")) {
+                            // Reload the ad.
+                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+                        }
 
-            // Go back.
-            mainWebView.goBack();
-        } else {  // There are no pages to go back to.
-            // Load a blank page
-            loadUrl("");
-        }
-    }
+                        // Remove the `SYSTEM_UI` flags from the root frame layout.
+                        rootFrameLayout.setSystemUiVisibility(0);
 
-    @Override
-    public void onSslMismatchProceed() {
-        // Do not check the pinned SSL certificate for this domain again until the domain changes.
-        ignorePinnedSslCertificate = true;
-    }
+                        // Add the translucent status flag.
+                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                    }
 
-    @Override
-    public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
-        // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-        formattedUrlString = "";
+                    // Consume the double-tap.
+                    return true;
+                } else { // Do not consume the double-tap because full screen browsing mode is disabled.
+                    return false;
+                }
+            }
+        });
 
-        // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-        navigatingHistory = true;
+        // Pass all touch events on the WebView through the double-tap gesture detector.
+        nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
+            // Call `performClick()` on the view, which is required for accessibility.
+            view.performClick();
 
-        // Load the history entry.
-        mainWebView.goBackOrForward(moveBackOrForwardSteps);
-    }
+            // Send the event to the gesture detector.
+            return doubleTapGestureDetector.onTouchEvent(event);
+        });
 
-    @Override
-    public void onClearHistory() {
-        // Clear the history.
-        mainWebView.clearHistory();
-    }
+        // Register the WebView for a context menu.  This is used to see link targets and download images.
+        registerForContextMenu(nestedScrollWebView);
 
-    // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
-    @Override
-    public void onBackPressed() {
-        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
-            // Close the navigation drawer.
-            drawerLayout.closeDrawer(GravityCompat.START);
-        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
-            if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
-                // close the bookmarks drawer.
-                drawerLayout.closeDrawer(GravityCompat.END);
-            } else {  // A subfolder is displayed.
-                // Place the former parent folder in `currentFolder`.
-                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder);
+        // Allow the downloading of files.
+        nestedScrollWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+            // Check if the download should be processed by an external app.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                // Create a download intent.  Not specifying the action type will display the maximum number of options.
+                Intent downloadIntent = new Intent();
 
-                // Load the new folder.
-                loadBookmarksFolder();
-            }
+                // Set the URI and the MIME type.  Specifying `text/html` displays a good number of options.
+                downloadIntent.setDataAndType(Uri.parse(url), "text/html");
 
-        } else if (mainWebView.canGoBack()) {  // There is at least one item in the `WebView` history.
-            // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-            formattedUrlString = "";
+                // Flag the intent to open in a new task.
+                downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-            navigatingHistory = true;
+                // Show the chooser.
+                startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
+            } else {  // Download with Android's download manager.
+                // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
+                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission has not been granted.
+                    // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
 
-            // Go back.
-            mainWebView.goBack();
-        } else {  // There isn't anything to do in Privacy Browser.
-            // Pass `onBackPressed()` to the system.
-            super.onBackPressed();
-        }
-    }
+                    // Store the variables for future use by `onRequestPermissionsResult()`.
+                    downloadUrl = url;
+                    downloadContentDisposition = contentDisposition;
+                    downloadContentLength = contentLength;
 
-    // Process the results of an upload file chooser.  Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // File uploads only work on API >= 21.
-        if (Build.VERSION.SDK_INT >= 21) {
-            // Pass the file to the WebView.
-            fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
-        }
-    }
+                    // Show a dialog if the user has previously denied the permission.
+                    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                        // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                        DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
 
-    private void loadUrlFromTextBox() {
-        // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
-        String unformattedUrlString = urlTextBox.getText().toString().trim();
+                        // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                        downloadLocationPermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.download_location));
+                    } else {  // Show the permission request directly.
+                        // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                    }
+                } else {  // The storage permission has already been granted.
+                    // Get a handle for the download file alert dialog.
+                    DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
 
-        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
-        if (unformattedUrlString.startsWith("content://")) {
-            // Load the entire content URL.
-            formattedUrlString = unformattedUrlString;
-        } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
-                || unformattedUrlString.startsWith("file://")) {
-            // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
-            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
-                unformattedUrlString = "https://" + unformattedUrlString;
+                    // Show the download file alert dialog.
+                    downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                }
             }
+        });
 
-            // Initialize `unformattedUrl`.
-            URL unformattedUrl = null;
+        // Update the find on page count.
+        nestedScrollWebView.setFindListener(new WebView.FindListener() {
+            // Get a handle for `findOnPageCountTextView`.
+            final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
 
-            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
-            try {
-                unformattedUrl = new URL(unformattedUrlString);
-            } catch (MalformedURLException e) {
-                e.printStackTrace();
+            @Override
+            public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
+                if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
+                    // Set `findOnPageCountTextView` to `0/0`.
+                    findOnPageCountTextView.setText(R.string.zero_of_zero);
+                } else if (isDoneCounting) {  // There are matches.
+                    // `activeMatchOrdinal` is zero-based.
+                    int activeMatch = activeMatchOrdinal + 1;
+
+                    // Build the match string.
+                    String matchString = activeMatch + "/" + numberOfMatches;
+
+                    // Set `findOnPageCountTextView`.
+                    findOnPageCountTextView.setText(matchString);
+                }
             }
+        });
 
-            // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
-            String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
-            String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
-            String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
-            String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
-            String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
+        if (Build.VERSION.SDK_INT >= 23) {
+            nestedScrollWebView.setOnScrollChangeListener((View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) -> {
+                // Update the status of swipe to refresh if it is enabled.
+                if (nestedScrollWebView.getSwipeToRefresh()) {
+                    // Only enable swipe to refresh if the WebView is scrolled to the top.
+                    swipeRefreshLayout.setEnabled(scrollY == 0);
+                }
+            });
+        }
 
-            // Build the URI.
-            Uri.Builder formattedUri = new Uri.Builder();
-            formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
+        // Set the web chrome client.
+        nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
+            // Update the progress bar when a page is loading.
+            @Override
+            public void onProgressChanged(WebView view, int progress) {
+                // Inject the night mode CSS if night mode is enabled.
+                if (nestedScrollWebView.getNightMode()) {
+                    // `background-color: #212121` sets the background to be dark gray.  `color: #BDBDBD` sets the text color to be light gray.  `box-shadow: none` removes a lower underline on links
+                    // used by WordPress.  `text-decoration: none` removes all text underlines.  `text-shadow: none` removes text shadows, which usually have a hard coded color.
+                    // `border: none` removes all borders, which can also be used to underline text.  `a {color: #1565C0}` sets links to be a dark blue.
+                    // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
+                    nestedScrollWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
+                            "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
+                            "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
+                        // Initialize a handler to display `mainWebView`.
+                        Handler displayWebViewHandler = new Handler();
+
+                        // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
+                        Runnable displayWebViewRunnable = () -> {
+                            // Only display `mainWebView` if the progress bar is gone.  This prevents the display of the `WebView` while it is still loading.
+                            if (progressBar.getVisibility() == View.GONE) {
+                                nestedScrollWebView.setVisibility(View.VISIBLE);
+                            }
+                        };
 
-            // Decode `formattedUri` as a `String` in `UTF-8`.
-            try {
-                formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                // Load a blank string.
-                formattedUrlString = "";
-            }
-        } else if (unformattedUrlString.isEmpty()){  // Load a blank web site.
-            // Load a blank string.
-            formattedUrlString = "";
-        } else {  // Search for the contents of the URL box.
-            // Create an encoded URL String.
-            String encodedUrlString;
+                        // Display the WebView after 500 milliseconds.
+                        displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
+                    });
+                }
 
-            // Sanitize the search input.
-            try {
-                encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                encodedUrlString = "";
-            }
+                // Update the progress bar.
+                progressBar.setProgress(progress);
 
-            // Add the base search URL.
-            formattedUrlString = searchURL + encodedUrlString;
-        }
+                // Set the visibility of the progress bar.
+                if (progress < 100) {
+                    // Show the progress bar.
+                    progressBar.setVisibility(View.VISIBLE);
+                } else {
+                    // Hide the progress bar.
+                    progressBar.setVisibility(View.GONE);
 
-        // Clear the focus from the URL text box.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
-        urlTextBox.clearFocus();
+                    // Display the nested scroll WebView if night mode is disabled.
+                    // Because of a race condition between `applyDomainSettings` and `onPageStarted`,
+                    // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled.
+                    if (!nestedScrollWebView.getNightMode()) {
+                        nestedScrollWebView.setVisibility(View.VISIBLE);
+                    }
 
-        // Make it so.
-        loadUrl(formattedUrlString);
-    }
+                    //Stop the swipe to refresh indicator if it is running
+                    swipeRefreshLayout.setRefreshing(false);
+                }
+            }
 
-    private void loadUrl(String url) {// Apply any custom domain settings.
-        // Set the URL as the formatted URL string so that checking third-party requests works correctly.
-        formattedUrlString = url;
+            // Set the favorite icon when it changes.
+            @Override
+            public void onReceivedIcon(WebView view, Bitmap icon) {
+                // Only update the favorite icon if the website has finished loading.
+                if (progressBar.getVisibility() == View.GONE) {
+                    // Store the new favorite icon.
+                    nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
 
-        // Apply the domain settings.
-        applyDomainSettings(url, true, false);
+                    // Get the current page position.
+                    int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
 
-        // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
-        urlIsLoading = !url.equals("");
+                    // Get the current tab.
+                    TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
 
-        // Load the URL.
-        mainWebView.loadUrl(url, customHeaders);
-    }
+                    // Check to see if the tab has been populated.
+                    if (tab != null) {
+                        // Get the custom view from the tab.
+                        View tabView = tab.getCustomView();
 
-    public void findPreviousOnPage(View view) {
-        // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
-        mainWebView.findNext(false);
-    }
+                        // Check to see if the custom tab view has been populated.
+                        if (tabView != null) {
+                            // Get the favorite icon image view from the tab.
+                            ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
 
-    public void findNextOnPage(View view) {
-        // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
-        mainWebView.findNext(true);
-    }
+                            // Display the favorite icon in the tab.
+                            tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
+                        }
+                    }
+                }
+            }
 
-    public void closeFindOnPage(View view) {
-        // Delete the contents of `find_on_page_edittext`.
-        findOnPageEditText.setText(null);
+            // Save a copy of the title when it changes.
+            @Override
+            public void onReceivedTitle(WebView view, String title) {
+                // Get the current page position.
+                int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
 
-        // Clear the highlighted phrases.
-        mainWebView.clearMatches();
+                // Get the current tab.
+                TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
 
-        // Hide the Find on Page `RelativeLayout`.
-        findOnPageLinearLayout.setVisibility(View.GONE);
+                // Only populate the title text view if the tab has been fully created.
+                if (tab != null) {
+                    // Get the custom view from the tab.
+                    View tabView = tab.getCustomView();
 
-        // Show the URL app bar.
-        supportAppBar.setVisibility(View.VISIBLE);
+                    // Remove the incorrect warning below that the current tab view might be null.
+                    assert tabView != null;
 
-        // Hide the keyboard so we can see the webpage.  `0` indicates no additional flags.
-        inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
-    }
+                    // Get the title text view from the tab.
+                    TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
 
-    private void applyAppSettings() {
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+                    // Set the title as the tab text.
+                    tabTitleTextView.setText(title);
+                }
+            }
 
-        // Store the values from the shared preferences in variables.
-        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
-        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
-        proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
-        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
-        hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
-        translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
-        downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
+            // Enter full screen video.
+            @Override
+            public void onShowCustomView(View video, CustomViewCallback callback) {
+                // Get a handle for the full screen video frame layout.
+                FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
 
-        // Apply the proxy through Orbot settings.
-        applyProxyThroughOrbot(false);
+                // Set the full screen video flag.
+                displayingFullScreenVideo = true;
 
-        // Set Do Not Track status.
-        if (doNotTrackEnabled) {
-            customHeaders.put("DNT", "1");
-        } else {
-            customHeaders.remove("DNT");
-        }
+                // Pause the ad if this is the free flavor.
+                if (BuildConfig.FLAVOR.contentEquals("free")) {
+                    // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+                    AdHelper.pauseAd(findViewById(R.id.adview));
+                }
 
-        // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
-        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-            if (hideSystemBarsOnFullscreen) {  // Hide everything.
-                // Remove the translucent navigation setting if it is currently flagged.
-                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+                // Hide the keyboard.
+                inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
 
-                // Remove the translucent status bar overlay.
-                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                // Hide the main content relative layout.
+                mainContentRelativeLayout.setVisibility(View.GONE);
 
                 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
                 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
 
-                /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+                /* Hide the system bars.
+                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
                  */
-                rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-            } else {  // Hide everything except the status and navigation bars.
-                // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
-                rootCoordinatorLayout.setSystemUiVisibility(0);
+                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
 
-                // Add the translucent status flag if it is unset.
-                getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                // Disable the sliding drawers.
+                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
 
-                if (translucentNavigationBarOnFullscreen) {
-                    // Set the navigation bar to be translucent.
-                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-                } else {
-                    // Set the navigation bar to be black.
-                    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-                }
-            }
-        } else {  // Privacy Browser is not in full screen browsing mode.
-            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
-            inFullScreenBrowsingMode = false;
+                // Add the video view to the full screen video frame layout.
+                fullScreenVideoFrameLayout.addView(video);
 
-            // Show the `appBar` if `findOnPageLinearLayout` is not visible.
-            if (findOnPageLinearLayout.getVisibility() == View.GONE) {
-                appBar.show();
+                // Show the full screen video frame layout.
+                fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
             }
 
-            // Show the `BannerAd` in the free flavor.
-            if (BuildConfig.FLAVOR.contentEquals("free")) {
-                // Initialize the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
-            }
+            // Exit full screen video.
+            @Override
+            public void onHideCustomView() {
+                // Get a handle for the full screen video frame layout.
+                FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
 
-            // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
-            rootCoordinatorLayout.setSystemUiVisibility(0);
+                // Unset the full screen video flag.
+                displayingFullScreenVideo = false;
 
-            // Remove the translucent navigation bar flag if it is set.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+                // Remove all the views from the full screen video frame layout.
+                fullScreenVideoFrameLayout.removeAllViews();
 
-            // Add the translucent status flag if it is unset.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                // Hide the full screen video frame layout.
+                fullScreenVideoFrameLayout.setVisibility(View.GONE);
 
-            // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
-            rootCoordinatorLayout.setFitsSystemWindows(true);
-        }
-    }
+                // Enable the sliding drawers.
+                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
 
-    // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
-    // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
-    @SuppressWarnings("deprecation")
-    private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
-        // Get the current user agent.
-        String initialUserAgent = mainWebView.getSettings().getUserAgentString();
+                // Show the main content relative layout.
+                mainContentRelativeLayout.setVisibility(View.VISIBLE);
+
+                // Apply the appropriate full screen mode flags.
+                if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+                    // Hide the app bar if specified.
+                    if (hideAppBar) {
+                        // Hide the tab linear layout.
+                        tabsLinearLayout.setVisibility(View.GONE);
+
+                        // Hide the action bar.
+                        actionBar.hide();
+                    }
+
+                    // Hide the banner ad in the free flavor.
+                    if (BuildConfig.FLAVOR.contentEquals("free")) {
+                        AdHelper.hideAd(findViewById(R.id.adview));
+                    }
+
+                    // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+                    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+                    /* Hide the system bars.
+                     * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                     * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                     * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                     * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                     */
+                    rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                } else {  // Switch to normal viewing mode.
+                    // Remove the `SYSTEM_UI` flags from the root frame layout.
+                    rootFrameLayout.setSystemUiVisibility(0);
+
+                    // Add the translucent status flag.
+                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                }
+
+                // Reload the ad for the free flavor if not in full screen mode.
+                if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+                    // Reload the ad.
+                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+                }
+            }
+
+            // Upload files.
+            @Override
+            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
+                // Show the file chooser if the device is running API >= 21.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    // Store the file path callback.
+                    fileChooserCallback = filePathCallback;
 
-        // Initialize a variable to track if the user agent changes.
-        boolean userAgentChanged = false;
+                    // Create an intent to open a chooser based ont the file chooser parameters.
+                    Intent fileChooserIntent = fileChooserParams.createIntent();
 
-        // Parse the URL into a URI.
-        Uri uri = Uri.parse(url);
+                    // Open the file chooser.  Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
+                    startActivityForResult(fileChooserIntent, 0);
+                }
+                return true;
+            }
+        });
 
-        // Extract the domain from `uri`.
-        String hostName = uri.getHost();
-
-        // Initialize `loadingNewDomainName`.
-        boolean loadingNewDomainName;
-
-        // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
-        // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
-        //noinspection SimplifiableIfStatement
-        if ((hostName == null) || (currentDomainName == null)) {
-            loadingNewDomainName = true;
-        } else {  // Determine if `hostName` equals `currentDomainName`.
-            loadingNewDomainName = !hostName.equals(currentDomainName);
-        }
+        nestedScrollWebView.setWebViewClient(new WebViewClient() {
+            // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
+            // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
+            @Override
+            public boolean shouldOverrideUrlLoading(WebView view, String url) {
+                if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
+                    // Apply the domain settings for the new URL.  This doesn't do anything if the domain has not changed.
+                    boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
 
-        // Strings don't like to be null.
-        if (hostName == null) {
-            hostName = "";
-        }
+                    // Check if the user agent has changed.
+                    if (userAgentChanged) {
+                        // Manually load the URL.  The changing of the user agent will cause WebView to reload the previous URL.
+                        nestedScrollWebView.loadUrl(url, customHeaders);
 
-        // Only apply the domain settings if a new domain is being loaded.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
-        if (loadingNewDomainName) {
-            // Set the new `hostname` as the `currentDomainName`.
-            currentDomainName = hostName;
+                        // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
+                        return true;
+                    } else {
+                        // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
+                        return false;
+                    }
+                } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
+                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
-            // Reset `ignorePinnedSslCertificate`.
-            ignorePinnedSslCertificate = false;
+                    // Parse the url and set it as the data for the intent.
+                    emailIntent.setData(Uri.parse(url));
 
-            // Reset the favorite icon if specified.
-            if (resetFavoriteIcon) {
-                favoriteIconBitmap = favoriteIconDefaultBitmap;
-                favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
-            }
+                    // Open the email program in a new task instead of as part of Privacy Browser.
+                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+                    // Make it so.
+                    startActivity(emailIntent);
 
-            // Get a full cursor from `domainsDatabaseHelper`.
-            Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+                    return true;
+                } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
+                    // Open the dialer and load the phone number, but wait for the user to place the call.
+                    Intent dialIntent = new Intent(Intent.ACTION_DIAL);
 
-            // Initialize `domainSettingsSet`.
-            Set<String> domainSettingsSet = new HashSet<>();
+                    // Add the phone number to the intent.
+                    dialIntent.setData(Uri.parse(url));
 
-            // Get the domain name column index.
-            int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
+                    // Open the dialer in a new task instead of as part of Privacy Browser.
+                    dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-            // Populate `domainSettingsSet`.
-            for (int i = 0; i < domainNameCursor.getCount(); i++) {
-                // Move `domainsCursor` to the current row.
-                domainNameCursor.moveToPosition(i);
+                    // Make it so.
+                    startActivity(dialIntent);
 
-                // Store the domain name in `domainSettingsSet`.
-                domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
-            }
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+                    return true;
+                } else {  // Load a system chooser to select an app that can handle the URL.
+                    // Open an app that can handle the URL.
+                    Intent genericIntent = new Intent(Intent.ACTION_VIEW);
 
-            // Close `domainNameCursor.
-            domainNameCursor.close();
+                    // Add the URL to the intent.
+                    genericIntent.setData(Uri.parse(url));
 
-            // Initialize variables to track if domain settings will be applied and, if so, under which name.
-            domainSettingsApplied = false;
-            String domainNameInDatabase = null;
+                    // List all apps that can handle the URL instead of just opening the first one.
+                    genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
 
-            // Check the hostname.
-            if (domainSettingsSet.contains(hostName)) {
-                domainSettingsApplied = true;
-                domainNameInDatabase = hostName;
-            }
+                    // Open the app in a new task instead of as part of Privacy Browser.
+                    genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-            // Check all the subdomains of the host name against wildcard domains in the domain cursor.
-            while (!domainSettingsApplied && hostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
-                if (domainSettingsSet.contains("*." + hostName)) {  // Check the host name prepended by `*.`.
-                    // Apply the domain settings.
-                    domainSettingsApplied = true;
+                    // Start the app or display a snackbar if no app is available to handle the URL.
+                    try {
+                        startActivity(genericIntent);
+                    } catch (ActivityNotFoundException exception) {
+                        Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
+                    }
 
-                    // Store the applied domain names as it appears in the database.
-                    domainNameInDatabase = "*." + hostName;
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+                    return true;
                 }
-
-                // Strip out the lowest subdomain of of the host name.
-                hostName = hostName.substring(hostName.indexOf(".") + 1);
             }
 
+            // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
+            @Override
+            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+                // Get a handle for the navigation view.
+                NavigationView navigationView = findViewById(R.id.navigationview);
 
-            // Get a handle for the shared preference.
-            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
-
-            // Store the general preference information.
-            String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
-            String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
-            defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
-            boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
-            nightMode = sharedPreferences.getBoolean("night_mode", false);
-            boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
+                // Get a handle for the navigation menu.
+                Menu navigationMenu = navigationView.getMenu();
 
-            if (domainSettingsApplied) {  // The url has custom domain settings.
-                // Get a cursor for the current host and move it to the first position.
-                Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
-                currentHostDomainSettingsCursor.moveToFirst();
+                // Get a handle for the navigation requests menu item.  The menu is 0 based.
+                MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
 
-                // Get the settings from the cursor.
-                domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
-                javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
-                firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
-                thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
-                domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
-                // Form data can be removed once the minimum API >= 26.
-                saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
-                easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
-                easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
-                fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
-                fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
-                ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
-                blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
-                String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
-                int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
-                int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
-                int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
-                int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
-                pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
-                pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
-                pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
-                pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
-                pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
-                pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
-                pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
-
-                // Set `nightMode` according to `nightModeInt`.  If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
-                switch (nightModeInt) {
-                    case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
-                        nightMode = true;
-                        break;
+                // Create an empty web resource response to be used if the resource request is blocked.
+                WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
 
-                    case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
-                        nightMode = false;
-                        break;
-                }
+                // Reset the whitelist results tracker.
+                String[] whitelistResultStringArray = null;
 
-                // Store the domain JavaScript status.  This is used by the options menu night mode toggle.
-                domainSettingsJavaScriptEnabled = javaScriptEnabled;
+                // Initialize the third party request tracker.
+                boolean isThirdPartyRequest = false;
 
-                // Enable JavaScript if night mode is enabled.
-                if (nightMode) {
-                    javaScriptEnabled = true;
-                }
+                // Get the current URL.  `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
+                String currentBaseDomain = nestedScrollWebView.getCurrentDomainName();
 
-                // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
-                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
-                    pinnedDomainSslStartDate = null;
-                } else {
-                    pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
-                }
+                // Store a copy of the current domain for use in later requests.
+                String currentDomain = currentBaseDomain;
 
-                // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
-                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
-                    pinnedDomainSslEndDate = null;
-                } else {
-                    pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
-                }
+                // Nobody is happy when comparing null strings.
+                if ((currentBaseDomain != null) && (url != null)) {
+                    // Convert the request URL to a URI.
+                    Uri requestUri = Uri.parse(url);
 
-                // Close `currentHostDomainSettingsCursor`.
-                currentHostDomainSettingsCursor.close();
+                    // Get the request host name.
+                    String requestBaseDomain = requestUri.getHost();
 
-                // Apply the domain settings.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
-                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
-                mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
+                    // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
+                    if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
+                        // Determine the current base domain.
+                        while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                            // Remove the first subdomain.
+                            currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
+                        }
 
-                // Apply the form data setting if the API < 26.
-                if (Build.VERSION.SDK_INT < 26) {
-                    mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
-                }
+                        // Determine the request base domain.
+                        while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                            // Remove the first subdomain.
+                            requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
+                        }
 
-                // Apply the font size.
-                if (fontSize == 0) {  // Apply the default font size.
-                    mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
-                } else {  // Apply the specified font size.
-                    mainWebView.getSettings().setTextZoom(fontSize);
+                        // Update the third party request tracker.
+                        isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
+                    }
                 }
 
-                // Set third-party cookies status if API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
-                }
+                // Get the current WebView page position.
+                int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
 
-                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
-                // <https://redmine.stoutner.com/issues/160>
-                if (!urlIsLoading) {
-                    // Set the user agent.
-                    if (userAgentName.equals(getString(R.string.system_default_user_agent))) {  // Use the system default user agent.
-                        // Get the array position of the default user agent name.
-                        int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
+                // Determine if the WebView is currently displayed.
+                boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
 
-                        // Set the user agent according to the system default.
-                        switch (defaultUserAgentArrayPosition) {
-                            case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
-                                // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
-                                mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
-                                break;
+                // Block third-party requests if enabled.
+                if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
+                    // Add the result to the resource requests.
+                    nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_THIRD_PARTY, url});
 
-                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
-                                // Set the user agent to `""`, which uses the default value.
-                                mainWebView.getSettings().setUserAgentString("");
-                                break;
+                    // Increment the blocked requests counters.
+                    nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                    nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS);
 
-                            case SETTINGS_CUSTOM_USER_AGENT:
-                                // Set the custom user agent.
-                                mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
-                                break;
+                    // Update the titles of the blocklist menu items if the WebView is currently displayed.
+                    if (webViewDisplayed) {
+                        // Updating the UI must be run from the UI thread.
+                        activity.runOnUiThread(() -> {
+                            // Update the menu item titles.
+                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+                            // Update the options menu if it has been populated.
+                            if (optionsMenu != null) {
+                                optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                optionsMenu.findItem(R.id.block_all_third_party_requests).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
+                                        getString(R.string.block_all_third_party_requests));
+                            }
+                        });
+                    }
 
-                            default:
-                                // Get the user agent string from the user agent data array
-                                mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
-                        }
-                    } else {  // Set the user agent according to the stored name.
-                        // Get the array position of the user agent name.
-                        int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
+                    // Return an empty web resource response.
+                    return emptyWebResourceResponse;
+                }
 
-                        switch (userAgentArrayPosition) {
-                            case UNRECOGNIZED_USER_AGENT:  // The user agent name contains a custom user agent.
-                                mainWebView.getSettings().setUserAgentString(userAgentName);
-                                break;
+                // Check UltraPrivacy if it is enabled.
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)) {
+                    // Check the URL against UltraPrivacy.
+                    String[] ultraPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
 
-                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
-                                // Set the user agent to `""`, which uses the default value.
-                                mainWebView.getSettings().setUserAgentString("");
-                                break;
+                    // Process the UltraPrivacy results.
+                    if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraPrivacy's blacklist.
+                        // Add the result to the resource requests.
+                        nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
+                                ultraPrivacyResults[5]});
 
-                            default:
-                                // Get the user agent string from the user agent data array.
-                                mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY);
+
+                        // Update the titles of the blocklist menu items if the WebView is currently displayed.
+                        if (webViewDisplayed) {
+                            // Updating the UI must be run from the UI thread.
+                            activity.runOnUiThread(() -> {
+                                // Update the menu item titles.
+                                navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+                                // Update the options menu if it has been populated.
+                                if (optionsMenu != null) {
+                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
+                                }
+                            });
                         }
-                    }
-
-                    // Store the applied user agent string, which is used in the View Source activity.
-                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
-
-                    // Update the user agent change tracker.
-                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
-                }
-
-                // Set swipe to refresh.
-                switch (swipeToRefreshInt) {
-                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
-                        // Set swipe to refresh according to the default.
-                        swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
-                        break;
 
-                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
-                        // Enable swipe to refresh.
-                        swipeRefreshLayout.setEnabled(true);
-                        break;
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    } else if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraPrivacy's whitelist.
+                        // Add a whitelist entry to the resource requests array.
+                        nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
+                                ultraPrivacyResults[5]});
 
-                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
-                        // Disable swipe to refresh.
-                        swipeRefreshLayout.setEnabled(false);
+                        // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
+                        return null;
+                    }
                 }
 
-                // Set the loading of webpage images.
-                switch (displayWebpageImagesInt) {
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
-                        break;
+                // Check EasyList if it is enabled.
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)) {
+                    // Check the URL against EasyList.
+                    String[] easyListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
 
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(true);
-                        break;
+                    // Process the EasyList results.
+                    if (easyListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyList's blacklist.
+                        // Add the result to the resource requests.
+                        nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]});
 
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(false);
-                        break;
-                }
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST);
+
+                        // Update the titles of the blocklist menu items if the WebView is currently displayed.
+                        if (webViewDisplayed) {
+                            // Updating the UI must be run from the UI thread.
+                            activity.runOnUiThread(() -> {
+                                // Update the menu item titles.
+                                navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+                                // Update the options menu if it has been populated.
+                                if (optionsMenu != null) {
+                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
+                                }
+                            });
+                        }
 
-                // Set a green background on `urlTextBox` to indicate that custom domain settings are being used.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
-                if (darkTheme) {
-                    urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
-                } else {
-                    urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
-                }
-            } else {  // The new URL does not have custom domain settings.  Load the defaults.
-                // Store the values from `sharedPreferences` in variables.
-                javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
-                firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
-                thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
-                domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
-                saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
-                easyListEnabled = sharedPreferences.getBoolean("easylist", true);
-                easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
-                fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
-                fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
-                ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
-                blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
-
-                // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
-                if (nightMode) {
-                    javaScriptEnabled = true;
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    } else if (easyListResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) {  // The resource request matched EasyList's whitelist.
+                        // Update the whitelist result string array tracker.
+                        whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]};
+                    }
                 }
 
-                // Apply the default settings.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
-                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
-                mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
-                mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
-                swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
+                // Check EasyPrivacy if it is enabled.
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)) {
+                    // Check the URL against EasyPrivacy.
+                    String[] easyPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
 
-                // Apply the form data setting if the API < 26.
-                if (Build.VERSION.SDK_INT < 26) {
-                    mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
-                }
+                    // Process the EasyPrivacy results.
+                    if (easyPrivacyResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyPrivacy's blacklist.
+                        // Add the result to the resource requests.
+                        nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
+                                easyPrivacyResults[5]});
 
-                // Reset the pinned SSL certificate information.
-                domainSettingsDatabaseId = -1;
-                pinnedDomainSslCertificate = false;
-                pinnedDomainSslIssuedToCNameString = "";
-                pinnedDomainSslIssuedToONameString = "";
-                pinnedDomainSslIssuedToUNameString = "";
-                pinnedDomainSslIssuedByCNameString = "";
-                pinnedDomainSslIssuedByONameString = "";
-                pinnedDomainSslIssuedByUNameString = "";
-                pinnedDomainSslStartDate = null;
-                pinnedDomainSslEndDate = null;
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY);
+
+                        // Update the titles of the blocklist menu items if the WebView is currently displayed.
+                        if (webViewDisplayed) {
+                            // Updating the UI must be run from the UI thread.
+                            activity.runOnUiThread(() -> {
+                                // Update the menu item titles.
+                                navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+                                // Update the options menu if it has been populated.
+                                if (optionsMenu != null) {
+                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
+                                }
+                            });
+                        }
 
-                // Set third-party cookies status if API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    } else if (easyPrivacyResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) {  // The resource request matched EasyPrivacy's whitelist.
+                        // Update the whitelist result string array tracker.
+                        whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]};
+                    }
                 }
 
-                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
-                // <https://redmine.stoutner.com/issues/160>
-                if (!urlIsLoading) {
-                    // Get the array position of the user agent name.
-                    int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
-
-                    // Set the user agent.
-                    switch (userAgentArrayPosition) {
-                        case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
-                            // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
-                            mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
-                            break;
+                // Check Fanboy’s Annoyance List if it is enabled.
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
+                    // Check the URL against Fanboy's Annoyance List.
+                    String[] fanboysAnnoyanceListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
 
-                        case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
-                            // Set the user agent to `""`, which uses the default value.
-                            mainWebView.getSettings().setUserAgentString("");
-                            break;
+                    // Process the Fanboy's Annoyance List results.
+                    if (fanboysAnnoyanceListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Annoyance List's blacklist.
+                        // Add the result to the resource requests.
+                        nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
+                                fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]});
 
-                        case SETTINGS_CUSTOM_USER_AGENT:
-                            // Set the custom user agent.
-                            mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
-                            break;
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST);
+
+                        // Update the titles of the blocklist menu items if the WebView is currently displayed.
+                        if (webViewDisplayed) {
+                            // Updating the UI must be run from the UI thread.
+                            activity.runOnUiThread(() -> {
+                                // Update the menu item titles.
+                                navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+                                // Update the options menu if it has been populated.
+                                if (optionsMenu != null) {
+                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsMenu.findItem(R.id.fanboys_annoyance_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
+                                            getString(R.string.fanboys_annoyance_list));
+                                }
+                            });
+                        }
 
-                        default:
-                            // Get the user agent string from the user agent data array
-                            mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    } else if (fanboysAnnoyanceListResults[0].equals(BlockListHelper.REQUEST_ALLOWED)){  // The resource request matched Fanboy's Annoyance List's whitelist.
+                        // Update the whitelist result string array tracker.
+                        whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
+                                fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
                     }
+                } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+                    // Check the URL against Fanboy's Annoyance List.
+                    String[] fanboysSocialListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
+
+                    // Process the Fanboy's Social Blocking List results.
+                    if (fanboysSocialListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Social Blocking List's blacklist.
+                        // Add the result to the resource requests.
+                        nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
+                                fanboysSocialListResults[4], fanboysSocialListResults[5]});
 
-                    // Store the applied user agent string, which is used in the View Source activity.
-                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST);
+
+                        // Update the titles of the blocklist menu items if the WebView is currently displayed.
+                        if (webViewDisplayed) {
+                            // Updating the UI must be run from the UI thread.
+                            activity.runOnUiThread(() -> {
+                                // Update the menu item titles.
+                                navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+                                // Update the options menu if it has been populated.
+                                if (optionsMenu != null) {
+                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsMenu.findItem(R.id.fanboys_social_blocking_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
+                                            getString(R.string.fanboys_social_blocking_list));
+                                }
+                            });
+                        }
 
-                    // Update the user agent change tracker.
-                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    } else if (fanboysSocialListResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) {  // The resource request matched Fanboy's Social Blocking List's whitelist.
+                        // Update the whitelist result string array tracker.
+                        whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
+                                fanboysSocialListResults[4], fanboysSocialListResults[5]};
+                    }
                 }
 
-                // Set the loading of webpage images.
-                mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
+                // Add the request to the log because it hasn't been processed by any of the previous checks.
+                if (whitelistResultStringArray != null) {  // The request was processed by a whitelist.
+                    nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
+                } else {  // The request didn't match any blocklist entry.  Log it as a default request.
+                    nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_DEFAULT, url});
+                }
 
-                // Set a transparent background on `urlTextBox`.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
-                urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
+                // The resource request has not been blocked.  `return null` loads the requested resource.
+                return null;
             }
 
-            // Close the domains database helper.
-            domainsDatabaseHelper.close();
+            // Handle HTTP authentication requests.
+            @Override
+            public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
+                // Store the handler.
+                nestedScrollWebView.setHttpAuthHandler(handler);
 
-            // Update the privacy icons, but only if `mainMenu` has already been populated.
-            if (mainMenu != null) {
-                updatePrivacyIcons(true);
+                // Instantiate an HTTP authentication dialog.
+                DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId());
+
+                // Show the HTTP authentication dialog.
+                httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
             }
-        }
 
-        // Reload the website if returning from the Domains activity.
-        if (reloadWebsite) {
-            mainWebView.reload();
-        }
+            @Override
+            public void onPageStarted(WebView view, String url, Bitmap favicon) {
+                // Get the theme preference.
+                boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-        // Return the user agent changed status.
-        return userAgentChanged;
-    }
+                // Reset the list of resource requests.
+                nestedScrollWebView.clearResourceRequests();
 
-    private void applyProxyThroughOrbot(boolean reloadWebsite) {
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+                // Reset the requests counters.
+                nestedScrollWebView.resetRequestsCounters();
 
-        // Get the search preferences.
-        String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
-        String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
-        String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
-        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
-        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
-        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
+                // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
+                if (nestedScrollWebView.getNightMode()) {
+                    nestedScrollWebView.setVisibility(View.INVISIBLE);
+                }
 
-        // Set the homepage, search, and proxy options.
-        if (proxyThroughOrbot) {  // Set the Tor options.
-            // Set `torHomepageString` as `homepage`.
-            homepage = torHomepageString;
+                // Hide the keyboard.
+                inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
 
-            // If formattedUrlString is null assign the homepage to it.
-            if (formattedUrlString == null) {
-                formattedUrlString = homepage;
-            }
+                // Check to see if Privacy Browser is waiting on Orbot.
+                if (!waitingForOrbot) {  // Process the URL.
+                    // Get the current page position.
+                    int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
 
-            // Set the search URL.
-            if (torSearchString.equals("Custom URL")) {  // Get the custom URL string.
-                searchURL = torSearchCustomUrlString;
-            } else {  // Use the string from the pre-built list.
-                searchURL = torSearchString;
-            }
+                    // Update the URL text bar if the page is currently selected.
+                    if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
+                        // Display the formatted URL text.
+                        urlEditText.setText(url);
 
-            // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
-            OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
+                        // Apply text highlighting to `urlTextBox`.
+                        highlightUrlText();
+                    }
 
-            // Set the `appBar` background to indicate proxying through Orbot is enabled.  `this` refers to the context.
-            if (darkTheme) {
-                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
-            } else {
-                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
-            }
+                    // Reset the list of host IP addresses.
+                    nestedScrollWebView.clearCurrentIpAddresses();
 
-            // Check to see if Orbot is ready.
-            if (!orbotStatus.equals("ON")) {  // Orbot is not ready.
-                // Set `waitingForOrbot`.
-                waitingForOrbot = true;
+                    // Get a URI for the current URL.
+                    Uri currentUri = Uri.parse(url);
 
-                // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
-                mainWebView.getSettings().setUseWideViewPort(false);
+                    // Get the IP addresses for the host.
+                    new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
 
-                // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
-                mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
-            } else if (reloadWebsite) {  // Orbot is ready and the website should be reloaded.
-                // Reload the website.
-                mainWebView.reload();
-            }
-        } else {  // Set the non-Tor options.
-            // Set `homepageString` as `homepage`.
-            homepage = homepageString;
+                    // Apply any custom domain settings if the URL was loaded by navigating history.
+                    if (nestedScrollWebView.getNavigatingHistory()) {
+                        // Reset navigating history.
+                        nestedScrollWebView.setNavigatingHistory(false);
 
-            // If formattedUrlString is null assign the homepage to it.
-            if (formattedUrlString == null) {
-                formattedUrlString = homepage;
-            }
+                        // Apply the domain settings.
+                        boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
 
-            // Set the search URL.
-            if (searchString.equals("Custom URL")) {  // Get the custom URL string.
-                searchURL = searchCustomUrlString;
-            } else {  // Use the string from the pre-built list.
-                searchURL = searchString;
-            }
+                        // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
+                        if (userAgentChanged) {
+                            loadUrl(url);
+                        }
+                    }
 
-            // Reset the proxy to default.  The host is `""` and the port is `"0"`.
-            OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
+                    // Replace Refresh with Stop if the options menu has been created.  (The WebView typically begins loading before the menu items are instantiated.)
+                    if (optionsMenu != null) {
+                        // Get a handle for the refresh menu item.
+                        MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
 
-            // Set the default `appBar` background.  `this` refers to the context.
-            if (darkTheme) {
-                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
-            } else {
-                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
-            }
+                        // Set the title.
+                        refreshMenuItem.setTitle(R.string.stop);
 
-            // Reset `waitingForOrbot.
-            waitingForOrbot = false;
+                        // Get the app bar and theme preferences.
+                        boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
 
-            // Reload the website if requested.
-            if (reloadWebsite) {
-                mainWebView.reload();
+                        // If the icon is displayed in the AppBar, set it according to the theme.
+                        if (displayAdditionalAppBarIcons) {
+                            if (darkTheme) {
+                                refreshMenuItem.setIcon(R.drawable.close_dark);
+                            } else {
+                                refreshMenuItem.setIcon(R.drawable.close_light);
+                            }
+                        }
+                    }
+                }
             }
-        }
-    }
 
-    private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
-        // Get handles for the menu items.
-        MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
-        MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
-        MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
-
-        // Update the privacy icon.
-        if (javaScriptEnabled) {  // JavaScript is enabled.
-            privacyMenuItem.setIcon(R.drawable.javascript_enabled);
-        } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
-            privacyMenuItem.setIcon(R.drawable.warning);
-        } else {  // All the dangerous features are disabled.
-            privacyMenuItem.setIcon(R.drawable.privacy_mode);
-        }
-
-        // Update the first-party cookies icon.
-        if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
-            firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
-        } else {  // First-party cookies are disabled.
-            if (darkTheme) {
-                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
-            } else {
-                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
-            }
-        }
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                // Reset the wide view port if it has been turned off by the waiting for Orbot message.
+                if (!waitingForOrbot) {
+                    // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
+                    nestedScrollWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
+                }
 
-        // Update the DOM storage icon.
-        if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
-            domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
-        } else if (javaScriptEnabled) {  // JavaScript is enabled but DOM storage is disabled.
-            if (darkTheme) {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
-            } else {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
-            }
-        } else {  // JavaScript is disabled, so DOM storage is ghosted.
-            if (darkTheme) {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
-            } else {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
-            }
-        }
+                // Flush any cookies to persistent storage.  The cookie manager has become very lazy about flushing cookies in recent versions.
+                if (nestedScrollWebView.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) {
+                    CookieManager.getInstance().flush();
+                }
 
-        // Update the refresh icon.
-        if (darkTheme) {
-            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
-        } else {
-            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
-        }
+                // Update the Refresh menu item if the options menu has been created.
+                if (optionsMenu != null) {
+                    // Get a handle for the refresh menu item.
+                    MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
 
-        // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
-        if (runInvalidateOptionsMenu) {
-            invalidateOptionsMenu();
-        }
-    }
+                    // Reset the Refresh title.
+                    refreshMenuItem.setTitle(R.string.refresh);
 
-    private void openUrlWithExternalApp(String url) {
-        // Create a download intent.  Not specifying the action type will display the maximum number of options.
-        Intent downloadIntent = new Intent();
+                    // Get the app bar and theme preferences.
+                    boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+                    boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-        // Set the URI and the mime type.  `"*/*"` will display the maximum number of options.
-        downloadIntent.setDataAndType(Uri.parse(url), "text/html");
+                    // If the icon is displayed in the AppBar, reset it according to the theme.
+                    if (displayAdditionalAppBarIcons) {
+                        if (darkTheme) {
+                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
+                        } else {
+                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
+                        }
+                    }
+                }
 
-        // Flag the intent to open in a new task.
-        downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                // Clear the cache and history if Incognito Mode is enabled.
+                if (incognitoModeEnabled) {
+                    // Clear the cache.  `true` includes disk files.
+                    nestedScrollWebView.clearCache(true);
 
-        // Show the chooser.
-        startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
-    }
+                    // Clear the back/forward history.
+                    nestedScrollWebView.clearHistory();
 
-    private void highlightUrlText() {
-        // Get the URL string.
-        String urlString = urlTextBox.getText().toString();
-
-        // Highlight the URL according to the protocol.
-        if (urlString.startsWith("file://")) {  // This is a file URL.
-            // De-emphasize only the protocol.
-            urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        } else if (urlString.startsWith("content://")) {
-            // De-emphasize only the protocol.
-            urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        } else {  // This is a web URL.
-            // Get the index of the `/` immediately after the domain name.
-            int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
-
-            // Create a base URL string.
-            String baseUrl;
-
-            // Get the base URL.
-            if (endOfDomainName > 0) {  // There is at least one character after the base URL.
-                // Get the base URL.
-                baseUrl = urlString.substring(0, endOfDomainName);
-            } else {  // There are no characters after the base URL.
-                // Set the base URL to be the entire URL string.
-                baseUrl = urlString;
-            }
+                    // Manually delete cache folders.
+                    try {
+                        // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
+                        // which links to `/data/data/com.stoutner.privacybrowser.standard`.
+                        String privateDataDirectoryString = getApplicationInfo().dataDir;
 
-            // Get the index of the last `.` in the domain.
-            int lastDotIndex = baseUrl.lastIndexOf(".");
+                        // Delete the main cache directory.
+                        Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
 
-            // Get the index of the penultimate `.` in the domain.
-            int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
+                        // Delete the secondary `Service Worker` cache directory.
+                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+                        Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+                    } catch (IOException e) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
 
-            // Markup the beginning of the URL.
-            if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
-                urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                // Update the URL text box and apply domain settings if not waiting on Orbot.
+                if (!waitingForOrbot) {
+                    // Get the current page position.
+                    int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
+
+                    // Update the URL text bar if the page is currently selected.
+                    if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
+                        // Check to see if the URL is `about:blank`.
+                        if (url.equals("about:blank")) {  // The WebView is blank.
+                            // Display the hint in the URL edit text.
+                            urlEditText.setText("");
+
+                            // Request focus for the URL text box.
+                            urlEditText.requestFocus();
+
+                            // Display the keyboard.
+                            inputMethodManager.showSoftInput(urlEditText, 0);
+
+                            // Hide the WebView, which causes the default background color to be displayed according to the theme.  // TODO
+                            nestedScrollWebView.setVisibility(View.GONE);
+
+                            // Apply the domain settings.  This clears any settings from the previous domain.
+                            applyDomainSettings(nestedScrollWebView, "", true, false);
+                        } else {  // The WebView has loaded a webpage.
+                            // Only update the URL text box if the user is not typing in it.
+                            if (!urlEditText.hasFocus()) {
+                                // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
+                                urlEditText.setText(nestedScrollWebView.getUrl());
+
+                                // Apply text highlighting to the URL.
+                                highlightUrlText();
+                            }
+                        }
+                    }
 
-                // De-emphasize subdomains.
-                if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
-                    urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-            } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
-                if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
-                    // De-emphasize the protocol and the additional subdomains.
-                    urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {  // There is only one subdomain in the domain name.
-                    // De-emphasize only the protocol.
-                    urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
+                    if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
+                            !nestedScrollWebView.ignorePinnedDomainInformation()) {
+                        CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
+                    }
                 }
             }
 
-            // De-emphasize the text after the domain name.
-            if (endOfDomainName > 0) {
-                urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-            }
-        }
-    }
+            // Handle SSL Certificate errors.
+            @Override
+            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+                // Get the current website SSL certificate.
+                SslCertificate currentWebsiteSslCertificate = error.getCertificate();
 
-    private void loadBookmarksFolder() {
-        // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+                // Extract the individual pieces of information from the current website SSL certificate.
+                String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+                String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
+                String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
+                String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
+                String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
+                String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
+                Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
+                Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
 
-        // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
-        bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
-            @Override
-            public View newView(Context context, Cursor cursor, ViewGroup parent) {
-                // Inflate the individual item layout.  `false` does not attach it to the root.
-                return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
+                // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
+                if (nestedScrollWebView.hasPinnedSslCertificate()) {
+                    // Get the pinned SSL certificate.
+                    ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
+
+                    // Extract the arrays from the array list.
+                    String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
+                    Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
+
+                    // Check if the current SSL certificate matches the pinned certificate.
+                    if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
+                        currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) &&
+                        currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) &&
+                        currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
+
+                        // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
+                        handler.proceed();
+                    }
+                } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
+                    // Store the SSL error handler.
+                    nestedScrollWebView.setSslErrorHandler(handler);
+
+                    // Instantiate an SSL certificate error alert dialog.
+                    DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
+
+                    // Show the SSL certificate error dialog.
+                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                }
             }
+        });
 
-            @Override
-            public void bindView(View view, Context context, Cursor cursor) {
-                // Get handles for the views.
-                ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
-                TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
+        // Check to see if this is the first page.
+        if (pageNumber == 0) {
+            // Set this nested scroll WebView as the current WebView.
+            currentWebView = nestedScrollWebView;
 
-                // Get the favorite icon byte array from the cursor.
-                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
+            // Apply the app settings from the shared preferences.
+            applyAppSettings();
 
-                // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
-                Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
+            // Load the website if not waiting for Orbot to connect.
+            if (!waitingForOrbot) {
+                // Get the intent that started the app.
+                Intent launchingIntent = getIntent();
 
-                // Display the bitmap in `bookmarkFavoriteIcon`.
-                bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
+                // Get the information from the intent.
+                String launchingIntentAction = launchingIntent.getAction();
+                Uri launchingIntentUriData = launchingIntent.getData();
 
-                // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
-                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
-                bookmarkNameTextView.setText(bookmarkNameString);
+                // If the intent action is a web search, perform the search.
+                if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+                    // Create an encoded URL string.
+                    String encodedUrlString;
 
-                // Make the font bold for folders.
-                if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
-                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
-                } else {  // Reset the font to default for normal bookmarks.
-                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
+                    // Sanitize the search input and convert it to a search.
+                    try {
+                        encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
+                    } catch (UnsupportedEncodingException exception) {
+                        encodedUrlString = "";
+                    }
+
+                    // Load the completed search URL.
+                    loadUrl(searchURL + encodedUrlString);
+                } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
+                    // Load the URL from the intent.
+                    loadUrl(launchingIntentUriData.toString());
+                } else {  // The is no URL in the intent.
+                    // Select the homepage based on the proxy through Orbot status.
+                    if (proxyThroughOrbot) {
+                        // Load the Tor homepage.
+                        loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
+                    } else {
+                        // Load the normal homepage.
+                        loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
+                    }
                 }
             }
-        };
-
-        // Populate the `ListView` with the adapter.
-        bookmarksListView.setAdapter(bookmarksCursorAdapter);
-
-        // Set the bookmarks drawer title.
-        if (currentBookmarksFolder.isEmpty()) {
-            bookmarksTitleTextView.setText(R.string.bookmarks);
-        } else {
-            bookmarksTitleTextView.setText(currentBookmarksFolder);
         }
     }
 }
\ No newline at end of file