]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Teach resource requests to be tab aware.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index a174230d851af1cd9444184b5769d182a51f81ca..e6ec1b71cebebd8dc39c490b79c1ba37d50cb656 100644 (file)
@@ -46,7 +46,6 @@ import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.net.http.SslError;
-import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
@@ -103,29 +102,31 @@ import androidx.core.content.ContextCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentPagerAdapter;
 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.PinnedMismatchDialog;
+import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
@@ -134,8 +135,6 @@ import com.stoutner.privacybrowser.helpers.BlockListHelper;
 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
 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;
@@ -143,18 +142,14 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
-import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
-import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -171,16 +166,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `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()`, `onCreateHomeScreenShortcut()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
+    // `favoriteIconBitmap` is public static so it can be accessed from `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkFolderDialog`,
+    // `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`.  It is also used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
+    // `onCreateHomeScreenShortcut()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
     public static Bitmap favoriteIconBitmap;
 
     // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()` and `applyDomainSettings`.
     public static Bitmap favoriteIconDefaultBitmap;
 
-    // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`,
-    // and `PinnedMismatchDialog`.
+    // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, and `PinnedMismatchDialog`.
     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
     public static String formattedUrlString;
 
@@ -188,16 +182,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // It is also used in `onCreate()` and `checkPinnedMismatch()`.
     public static SslCertificate sslCertificate;
 
-    // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`.
+    // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment`, `GetHostIpAddresses()`, and `ViewSslCertificateDialog`.
     // It is also used in `onCreate()` and `GetHostIpAddresses()`.
     public static String currentHostIpAddresses;
 
+    // The getting IP addresses tracker is used in `onCreate() and `GetHostIpAddresses`.
+    public static boolean gettingIpAddresses;
+
+    // The URL loading tracker is public static so it can be accessed from `GetHostIpAddresses`.
+    // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`.
+    public static boolean urlIsLoading;
+
     // `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`.  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;
 
@@ -210,60 +208,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `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()`.
+    // The blocklist 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;
@@ -272,9 +223,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     public static String currentBookmarksFolder;
 
-    // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
-    public static int domainSettingsDatabaseId;
-
     // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`.  They are also used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
     public static String pinnedSslIssuedToCName;
     public static String pinnedSslIssuedToOName;
@@ -296,12 +244,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
 
 
-    // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`.
-    private static boolean urlIsLoading;
-
-    // `gettingIpAddresses` is used in `onCreate() and `GetHostIpAddresses`.
-    private static boolean gettingIpAddresses;
-
     // `pinnedDomainSslCertificate` is used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
     private static boolean pinnedSslCertificate;
 
@@ -322,7 +264,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean navigatingHistory;
 
     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
-    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
+    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`.
     private NestedScrollWebView currentWebView;
 
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
@@ -441,9 +383,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `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;
 
@@ -462,9 +401,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
     private ActionBarDrawerToggle actionBarDrawerToggle;
 
-    // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
-    private EditText urlTextBox;
-
     // The color spans are used in `onCreate()` and `highlightUrlText()`.
     private ForegroundColorSpan redColorSpan;
     private ForegroundColorSpan initialGrayColorSpan;
@@ -568,7 +504,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // This is needed to get rid of the Android Studio warning that the action bar might be null.
         assert actionBar != null;
 
-        // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
+        // Add the custom layout, which shows the URL text bar.
         actionBar.setCustomView(R.layout.url_app_bar);
         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
@@ -578,18 +514,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
 
         // Get a handle for `urlTextBox`.
-        urlTextBox = findViewById(R.id.url_edittext);
+        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();
@@ -597,7 +533,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.
@@ -643,9 +579,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Instantiate the block list helper.
         blockListHelper = new BlockListHelper();
 
-        // Initialize the list of resource requests.
-        resourceRequests = new ArrayList<>();
-
         // Parse the block lists.
         easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
         easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
@@ -703,12 +636,44 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             @Override
             public void onPageSelected(int position) {
-                // TODO.  Consider using an array of the WebViews.
-                // Get the current WebView fragment.  Instantiate item returns the current item if it already exists.
-                Fragment webViewFragment = (Fragment) webViewPagerAdapter.instantiateItem(webViewPager, position);
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(position);
+
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
+
+                // Remove the incorrect lint warning below that the fragment view might be null.
+                assert fragmentView != null;
 
                 // Store the current WebView.
-                currentWebView = (NestedScrollWebView) webViewFragment.getView();
+                currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+                // Store the current formatted URL string.
+                formattedUrlString = currentWebView.getUrl();
+
+                // Clear the focus from the URL text box.
+                urlEditText.clearFocus();
+
+                // Hide the soft keyboard.
+                inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+
+                // Display the current URL in the URL text box.
+                urlEditText.setText(formattedUrlString);
+
+                // Highlight the URL text.
+                highlightUrlText();
+
+                // Set the background to indicate the domain settings status.
+                if (currentWebView.getDomainSettingsApplied()) {
+                    // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
+                    if (darkTheme) {
+                        urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
+                    } else {
+                        urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
+                    }
+                } else {
+                    urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
+                }
 
                 // 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.
                 if (tabLayout.getSelectedTabPosition() != position) {
@@ -752,8 +717,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
-        // Add the first tab.
-        webViewPagerAdapter.addPage();
+        // Add the first tab.  (It doesn't matter what view is passed.  That is just required as part of the ImageView `onClick()` syntax).
+        addTab(webViewPager);
 
         // 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) {
@@ -770,6 +735,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
         launchBookmarksActivityFab.setOnClickListener(v -> {
+            // Store the current WebView url and title in the bookmarks activity.
+            BookmarksActivity.currentWebViewUrl = currentWebView.getUrl();
+            BookmarksActivity.currentWebViewTitle = currentWebView.getTitle();
+
             // Create an intent to launch the bookmarks activity.
             Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
 
@@ -789,8 +758,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Set the create new bookmark FAB to display an alert dialog.
         createBookmarkFab.setOnClickListener(view -> {
-            // Show the create bookmark dialog and name the instance `@string/create_bookmark`.
-            DialogFragment createBookmarkDialog = new CreateBookmarkDialog();
+            // Instantiate the create bookmark dialog.
+            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), favoriteIconBitmap);
+
+            // Display the create bookmark dialog.
             createBookmarkDialog.show(fragmentManager, resources.getString(R.string.create_bookmark));
         });
 
@@ -816,7 +787,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // 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.
+                // Hide the soft keyboard.
                 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
 
                 // Consume the event.
@@ -835,10 +806,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // 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.
@@ -953,13 +924,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
                     navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
                     navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
-                    navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                    navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
 
                     // Hide the keyboard (if displayed).
                     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();
+                    urlEditText.clearFocus();
                     currentWebView.clearFocus();
                 }
             }
@@ -995,11 +966,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         saveFormDataEnabled = false;  // Form data can be removed once the minimum API >= 26.
         nightMode = false;
 
+        // 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);
+
+        // Get a handle for the WebView.
+        WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
+
         // Store the default user agent.
-        // TODO webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
+        webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
 
-        // Initialize the WebView title.
-        webViewTitle = getString(R.string.no_title);
+        // Destroy the bare WebView.
+        bareWebView.destroy();
 
         // Initialize the favorite icon bitmap.  `ContextCompat` must be used until API >= 21.
         Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
@@ -1116,8 +1093,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Reload the webpage if displaying of images has been disabled in the Settings activity.
             if (reloadOnRestart) {
                 // Reload the WebViews.
-                // TODO
-                currentWebView.reload();
+                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 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);
+
+                        // TODO this doesn't seem to work if for WebViews that aren't visible.
+                        // Reload the WebView.
+                        nestedScrollWebView.reload();
+                    }
+                }
 
                 // Reset `reloadOnRestartBoolean`.
                 reloadOnRestart = false;
@@ -1170,11 +1162,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the default commands.
         super.onResume();
 
-        // Resume JavaScript (if enabled).
-        // TODO mainWebView.resumeTimers();
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-        // Resume `mainWebView`.
-        // TODO mainWebView.onResume();
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
+
+            // 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);
+
+                // Resume the nested scroll WebView JavaScript timers.
+                nestedScrollWebView.resumeTimers();
+
+                // Resume the nested scroll WebView.
+                nestedScrollWebView.onResume();
+            }
+        }
 
         // Display a message to the user if waiting for Orbot.
         if (waitingForOrbot && !orbotStatus.equals("ON")) {
@@ -1211,13 +1217,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the default commands.
         super.onPause();
 
-        // Pause `mainWebView`.
-        // TODO
-        currentWebView.onPause();
+        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 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);
 
-        // Stop all JavaScript.
-        // TODO
-        currentWebView.pauseTimers();
+                // Pause the nested scroll WebView.
+                nestedScrollWebView.onPause();
+
+                // Pause the nested scroll WebView JavaScript timers.
+                nestedScrollWebView.pauseTimers();
+            }
+        }
 
         // Pause the ad or it will continue to consume resources in the background on the free flavor.
         if (BuildConfig.FLAVOR.contentEquals("free")) {
@@ -1332,11 +1350,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
         MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
 
-        // 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);
+        // Initialize the current user agent string and the font size.
+        String currentUserAgent = getString(R.string.user_agent_privacy_browser);
+        int fontSize = 100;
+
+        // 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);
+            }
+
+            // 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 display images menu item.
+            displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
+
+            // 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_BLOCKED_REQUESTS) + " - " + getString(R.string.easylist));
+            easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.easyprivacy));
+            fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS) + " - " + getString(R.string.fanboys_annoyance_list));
+            fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS) + " - " +
+                    getString(R.string.fanboys_social_blocking_list));
+            ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.ultraprivacy));
+            blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
         }
 
         // Set the status of the menu item checkboxes.
@@ -1351,7 +1395,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
         blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
         swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
-        // TODO displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
         nightModeMenuItem.setChecked(nightMode);
         proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
 
@@ -1396,19 +1439,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
         fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
 
-        // 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));
-
-        // Get the current user agent.
-        // TODO String currentUserAgent = mainWebView.getSettings().getUserAgentString();
-        String currentUserAgent = "";
-
         // 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);
@@ -1438,9 +1468,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             menu.findItem(R.id.user_agent_custom).setChecked(true);
         }
 
-        // Initialize font size variables.
-        // TODO int fontSize = mainWebView.getSettings().getTextZoom();
-        int fontSize = 100;
+        // Instantiate the font size title and the selected font size menu item.
         String fontSizeTitle;
         MenuItem selectedFontSizeMenuItem;
 
@@ -1555,7 +1583,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 return true;
 
             case R.id.add_or_edit_domain:
-                if (domainSettingsApplied) {  // Edit the current domain settings.
+                if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
                     // Reapply the domain settings on returning to `MainWebViewActivity`.
                     reapplyDomainSettingsOnRestart = true;
                     currentDomainName = "";
@@ -1564,7 +1592,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
                     // 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("loadDomain", currentWebView.getDomainSettingsDatabaseId());
                     domainsIntent.putExtra("closeOnBack", true);
 
                     // Make it so.
@@ -2040,7 +2068,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 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.
+                } else if (currentWebView.getDomainSettingsApplied()) {  // 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.
@@ -2091,7 +2119,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case R.id.share_url:
                 // Setup the share string.
-                String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
+                String shareString = currentWebView.getTitle() + " – " + formattedUrlString;
 
                 // Create the share intent.
                 Intent shareIntent = new Intent(Intent.ACTION_SEND);
@@ -2178,7 +2206,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the current tab number.
                 int currentTabNumber = tabLayout.getSelectedTabPosition();
 
-                // Delete the tab and page.
+                // Delete the current tab.
+                tabLayout.removeTabAt(currentTabNumber);
+
+                // Delete the current page.
                 webViewPagerAdapter.deletePage(currentTabNumber);
                 break;
 
@@ -2369,8 +2400,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 break;
 
             case R.id.requests:
-                // Launch the requests activity.
+                // Populate the resource requests.
+                RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
+
+                // Create an intent to launch the Requests activity.
                 Intent requestsIntent = new Intent(this, RequestsActivity.class);
+
+                // Make it so.
                 startActivity(requestsIntent);
                 break;
 
@@ -3267,8 +3303,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void loadUrlFromTextBox() {
+        // Get a handle for the URL edit text.
+        EditText urlEditText = findViewById(R.id.url_edittext);
+
         // 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();
+        String unformattedUrlString = urlEditText.getText().toString().trim();
 
         // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
         if (unformattedUrlString.startsWith("content://")) {
@@ -3327,8 +3366,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             formattedUrlString = searchURL + encodedUrlString;
         }
 
-        // 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();
+        // 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();
 
         // Make it so.
         loadUrl(formattedUrlString);
@@ -3408,8 +3447,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             customHeaders.remove("DNT");
         }
 
-        // Set the app bar scrolling.
-        currentWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
+        // TODO this also needs to be set when creating a new tab.
+        // 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);
+
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
+
+            // 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);
+
+                // Set the app bar scrolling.
+                nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
+            }
+        }
 
         // Update the full screen browsing mode settings.
         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
@@ -3556,21 +3611,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Close `domainNameCursor.
             domainNameCursor.close();
 
-            // Initialize variables to track if domain settings will be applied and, if so, under which name.
-            domainSettingsApplied = false;
+            // Initialize the domain name in database variable.
             String domainNameInDatabase = null;
 
-            // Check the hostname.
-            if (domainSettingsSet.contains(hostName)) {
-                domainSettingsApplied = true;
+            // Check the hostname against the domain settings set.
+            if (domainSettingsSet.contains(hostName)) {  // The hostname is contained in the domain settings set.
+                // Record the domain name in the database.
                 domainNameInDatabase = hostName;
+
+                // Set the domain settings applied tracker to true.
+                currentWebView.setDomainSettingsApplied(true);
+            } else {  // The hostname is not contained in the domain settings set.
+                // Set the domain settings applied tracker to false.
+                currentWebView.setDomainSettingsApplied(false);
             }
 
             // 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.
+            while (!currentWebView.getDomainSettingsApplied() && 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;
+                    // Set the domain settings applied tracker to true.
+                    currentWebView.setDomainSettingsApplied(true);
 
                     // Store the applied domain names as it appears in the database.
                     domainNameInDatabase = "*." + hostName;
@@ -3592,13 +3652,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             nightMode = sharedPreferences.getBoolean("night_mode", false);
             boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
-            if (domainSettingsApplied) {  // The url has custom domain settings.
+            if (currentWebView.getDomainSettingsApplied()) {  // 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 the settings from the cursor.
-                domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
+                currentWebView.setDomainSettingsDatabaseId(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);
@@ -3772,7 +3832,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         break;
                 }
 
-                // 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.
+                // Set a green background on URL edit text to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
                 if (darkTheme) {
                     urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
                 } else {
@@ -3810,7 +3870,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Reset the pinned variables.
-                domainSettingsDatabaseId = -1;
+                currentWebView.setDomainSettingsDatabaseId(-1);
                 pinnedSslCertificate = false;
                 pinnedSslIssuedToCName = "";
                 pinnedSslIssuedToOName = "";
@@ -3866,7 +3926,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Set the loading of webpage images.
                 currentWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
 
-                // Set a transparent background on `urlTextBox`.  The deprecated `.getDrawable()` must be used until the minimum API >= 21.
+                // Set a transparent background on URL edit text.  The deprecated `.getDrawable()` must be used until the minimum API >= 21.
                 urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
             }
 
@@ -3976,9 +4036,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Reset `waitingForOrbot.
             waitingForOrbot = false;
 
-            // Reload the website if requested.
+            // Reload the WebViews if requested.
             if (reloadWebsite) {
-                currentWebView.reload();
+                // Reload the WebViews.
+                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 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);
+
+                        // Reload the WebView.
+                        nestedScrollWebView.reload();
+                    }
+                }
             }
         }
     }
@@ -4055,18 +4131,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     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 (!urlTextBox.hasFocus()) {
+        if (!urlEditText.hasFocus()) {
             // Get the URL string.
-            String urlString = urlTextBox.getText().toString();
+            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.
-                urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                urlEditText.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);
+                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));
@@ -4091,25 +4170,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // 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);
+                    urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
 
                     // 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);
+                        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.
-                        urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                        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.
-                        urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                        urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                     }
                 }
 
                 // De-emphasize the text after the domain name.
                 if (endOfDomainName > 0) {
-                    urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                 }
             }
         }
@@ -4194,7 +4273,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         startActivity(openWithBrowserIntent);
     }
 
-    private static void checkPinnedMismatch() {
+    public static void checkPinnedMismatch(int domainSettingsDatabaseId) {
         if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
             // Initialize the current SSL certificate variables.
             String currentWebsiteIssuedToCName = "";
@@ -4250,7 +4329,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
 
                 // Get a handle for the pinned mismatch alert dialog.
-                DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
+                DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(domainSettingsDatabaseId, pinnedSslCertificate, pinnedIpAddresses);
 
                 // Show the pinned mismatch alert dialog.
                 pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch");
@@ -4258,239 +4337,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
-    // This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `String` contains the results.
-    private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
-        // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
-        private final WeakReference<Activity> activityWeakReference;
-
-        GetHostIpAddresses(Activity activity) {
-            // Populate the weak references.
-            activityWeakReference = new WeakReference<>(activity);
-        }
-
-        // `onPreExecute()` operates on the UI thread.
-        @Override
-        protected void onPreExecute() {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return;
-            }
-
-            // Set the getting IP addresses tracker.
-            gettingIpAddresses = true;
-        }
-
-
-        @Override
-        protected String doInBackground(String... domainName) {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                // Return an empty spannable string builder.
-                return "";
-            }
-
-            // Initialize an IP address string builder.
-            StringBuilder ipAddresses = new StringBuilder();
-
-            // Get an array with the IP addresses for the host.
-            try {
-                // Get an array with all the IP addresses for the domain.
-                InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
-
-                // Add each IP address to the string builder.
-                for (InetAddress inetAddress : inetAddressesArray) {
-                    if (ipAddresses.length() == 0) {  // This is the first IP address.
-                        // Add the IP address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
-                    } else {  // This is not the first IP address.
-                        // Add a line break to the string builder first.
-                        ipAddresses.append("\n");
-
-                        // Add the IP address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
-                    }
-                }
-            } catch (UnknownHostException exception) {
-                // Do nothing.
-            }
-
-            // Return the string.
-            return ipAddresses.toString();
-        }
-
-        // `onPostExecute()` operates on the UI thread.
-        @Override
-        protected void onPostExecute(String ipAddresses) {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return;
-            }
-
-            // Store the IP addresses.
-            currentHostIpAddresses = ipAddresses;
-
-            if (!urlIsLoading) {
-                checkPinnedMismatch();
-            }
-
-            // Reset the getting IP addresses tracker.
-            gettingIpAddresses = false;
-        }
-    }
-
-    private class WebViewPagerAdapter extends FragmentPagerAdapter {
-        // The WebView fragments list contains all the WebViews.
-        private LinkedList<WebViewTabFragment> webViewFragmentsList = new LinkedList<>();
-
-        // Define the constructor.
-        private WebViewPagerAdapter(FragmentManager fragmentManager){
-            // Run the default commands.
-            super(fragmentManager);
-        }
-
-        @Override
-        public int getCount() {
-            // Return the number of pages.
-            return webViewFragmentsList.size();
-        }
-
-        @Override
-        public int getItemPosition(@NonNull Object object) {
-            //noinspection SuspiciousMethodCalls
-            if (webViewFragmentsList.contains(object)) {
-                // The tab has not been deleted.
-                return POSITION_UNCHANGED;
-            } else {
-                // The tab has been deleted.
-                return POSITION_NONE;
-            }
-        }
-
-        @Override
-        public Fragment getItem(int pageNumber) {
-            // Get a WebView for a particular page.  Page numbers are 0 indexed.
-            return webViewFragmentsList.get(pageNumber);
-        }
-
-        private void addPage() {
-            // Add a new page.  The pages and tabs are 0 indexed, so the size of the current list equals the number of the next page.
-            webViewFragmentsList.add(WebViewTabFragment.createTab(webViewFragmentsList.size()));
-
-            // Update the view pager.
-            notifyDataSetChanged();
-        }
-
-        private void deletePage(int pageNumber) {
-            // Get a handle for the tab layout.
-            TabLayout tabLayout = findViewById(R.id.tablayout);
-
-            // TODO always move to the next tab if possible.
-            // Select a tab that is not being deleted.
-            if (pageNumber == 0) {  // The first tab is being deleted.
-                // Get a handle for the second tab.  The tabs are 0 indexed.
-                TabLayout.Tab secondTab = tabLayout.getTabAt(1);
-
-                // Remove the incorrect lint warning below that the second tab might be null.
-                assert secondTab != null;
+    public void addTab(View view) {
+        // Get a handle for the tab layout.
+        TabLayout tabLayout = findViewById(R.id.tablayout);
 
-                // Select the second tab.
-                secondTab.select();
-            } else {  // The first tab is not being deleted.
-                // Get a handle for the previous tab.
-                TabLayout.Tab previousTab = tabLayout.getTabAt(pageNumber - 1);
+        // 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();
 
-                // Remove the incorrect lint warning below tha the previous tab might be null.
-                assert previousTab != null;
+        // Add a new tab.
+        tabLayout.addTab(tabLayout.newTab());
 
-                // Select the previous tab.
-                previousTab.select();
-            }
+        // Get the new tab.
+        TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber);
 
-            // Delete the page.
-            webViewFragmentsList.remove(pageNumber);
-
-            // Delete the tab.
-            tabLayout.removeTabAt(pageNumber);
+        // Remove the lint warning below that the current tab might be null.
+        assert newTab != null;
 
-            // Update the view pager.
-            notifyDataSetChanged();
-        }
-    }
+        // Set a custom view on the current tab.
+        newTab.setCustomView(R.layout.custom_tab_view);
 
-    public void addTab(View view) {
         // Add the new WebView page.
-        webViewPagerAdapter.addPage();
+        webViewPagerAdapter.addPage(newTabNumber);
 
-        // Get a handle for the tab layout.
-        TabLayout tabLayout = findViewById(R.id.tablayout);
-
-        // Get a handle for the new tab.  The tabs are 0 indexed.
-        TabLayout.Tab newTab = tabLayout.getTabAt(tabLayout.getTabCount() - 1);
-
-        // Remove the incorrect warning below that the new tab might be null.
-        assert newTab != null;
-
-        // Move the tab layout to the new tab.
-        newTab.select();
+        if (newTabNumber > 0) {
+            // Move to the new tab.
+            newTab.select();
+        }
     }
 
     @Override
-    public void initializeWebView(NestedScrollWebView nestedScrollWebView, int tabNumber) {
+    public void initializeWebView(long pageId, int pageNumber, ProgressBar progressBar, NestedScrollWebView nestedScrollWebView) {
         // Get handles for the activity views.
-        final FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-        final DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-        final RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
-        final ActionBar actionBar = getSupportActionBar();
-        final TabLayout tabLayout = findViewById(R.id.tablayout);
-        final SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
+        ActionBar actionBar = getSupportActionBar();
+        EditText urlEditText = findViewById(R.id.url_edittext);
+        TabLayout tabLayout = findViewById(R.id.tablayout);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
         // Remove the incorrect lint warnings below that the some of the views might be null.
         assert actionBar != null;
 
-        // TODO.  Still doesn't work right.
-        // Create the tab if it doesn't already exist.
-        try {
-            TabLayout.Tab tab = tabLayout.getTabAt(tabNumber);
-
-            assert tab != null;
-
-            tab.getCustomView();
-        } catch (Exception exception) {
-            tabLayout.addTab(tabLayout.newTab());
-        }
-
-        // Get the current tab.
-        TabLayout.Tab currentTab = tabLayout.getTabAt(tabNumber);
-
-        // Remove the lint warning below that the current tab might be null.
-        assert currentTab != null;
-
-        // Set a custom view on the current tab.
-        currentTab.setCustomView(R.layout.custom_tab_view);
-
-        // Get the custom view from the tab.
-        View currentTabView = currentTab.getCustomView();
-
-        // Remove the incorrect warning below that the current tab view might be null.
-        assert currentTabView != null;
-
-        // Get the current views from the tab.
-        ImageView tabFavoriteIconImageView = currentTabView.findViewById(R.id.favorite_icon_imageview);
-        TextView tabTitleTextView = currentTabView.findViewById(R.id.title_textview);
-
-
-        //TODO
-        final ProgressBar progressBar = findViewById(R.id.progress_bar);
-
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
@@ -4714,9 +4602,27 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Only update the favorite icon if the website has finished loading.
                 if (progressBar.getVisibility() == View.GONE) {
                     // Save a copy of the favorite icon.
-                    // TODO.  We need to save and access the icons for each tab.
                     favoriteIconBitmap = icon;
 
+                    // Get the current page position.
+                    int currentPosition = webViewPagerAdapter.getPositionForId(pageId);
+
+                    // Get the current tab.
+                    TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
+
+                    // Remove the lint warning below that the current tab might be null.
+                    assert tab != null;
+
+                    // Get the custom view from the tab.
+                    View tabView = tab.getCustomView();
+
+                    // Remove the incorrect warning below that the current tab view might be null.
+                    assert tabView != null;
+
+                    // Get the favorite icon image view from the tab.
+                    ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
+
+                    // Display the favorite icon in the tab.
                     tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
                 }
             }
@@ -4724,12 +4630,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Save a copy of the title when it changes.
             @Override
             public void onReceivedTitle(WebView view, String title) {
-                // Save a copy of the title.
-                // TODO.  Replace `webViewTitle` with `currentWebView.getTitle()`.
-                webViewTitle = title;
+                // Get the current page position.
+                int currentPosition = webViewPagerAdapter.getPositionForId(pageId);
+
+                // Get the current tab.
+                TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
+
+                // Remove the lint warning below that the current tab might be null.
+                assert tab != null;
+
+                // Get the custom view from the tab.
+                View tabView = tab.getCustomView();
+
+                // Remove the incorrect warning below that the current tab view might be null.
+                assert tabView != null;
+
+                // Get the title text view from the tab.
+                TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
 
                 // Set the title as the tab text.
-                tabTitleTextView.setText(webViewTitle);
+                tabTitleTextView.setText(title);
             }
 
             // Enter full screen video.
@@ -4936,7 +4856,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
 
                 // Reset the whitelist results tracker.
-                whiteListResultStringArray = null;
+                String[] whitelistResultStringArray = null;
 
                 // Initialize the third party request tracker.
                 boolean isThirdPartyRequest = false;
@@ -4976,21 +4896,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     }
                 }
 
+                // Get the current WebView page position.
+                int webViewPagePosition = webViewPagerAdapter.getPositionForId(pageId);
+
+                // Determine if the WebView is currently displayed.
+                boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
+
                 // Block third-party requests if enabled.
                 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
+                    // Add the result to the resource requests.
+                    nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_THIRD_PARTY, url});
+
                     // 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));
-                    });
+                    nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                    nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS);
 
-                    // Add the request to the log.
-                    resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
+                    // 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));
+                            blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                            blockAllThirdPartyRequestsMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS) + " - " +
+                                    getString(R.string.block_all_third_party_requests));
+                        });
+                    }
 
                     // Return an empty web resource response.
                     return emptyWebResourceResponse;
@@ -4998,26 +4929,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Check UltraPrivacy if it is enabled.
                 if (ultraPrivacyEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        ultraPrivacyBlockedRequests++;
+                    // Check the URL against UltraPrivacy.
+                    String[] ultraPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
 
-                        // 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));
-                        });
+                    // 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]});
+
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS);
+
+                        // 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));
+                                blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                ultraPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.ultraprivacy));
+                            });
+                        }
 
                         // The resource request was blocked.  Return an empty web resource response.
                         return emptyWebResourceResponse;
-                    }
-
-                    // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
-                    if (whiteListResultStringArray != null) {
+                    } else if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraPrivacy's whitelist.
                         // Add a whitelist entry to the resource requests array.
-                        resourceRequests.add(whiteListResultStringArray);
+                        nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
+                                ultraPrivacyResults[5]});
 
                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
                         return null;
@@ -5026,94 +4967,145 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Check EasyList if it is enabled.
                 if (easyListEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        easyListBlockedRequests++;
+                    // Check the URL against EasyList.
+                    String[] easyListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
 
-                        // 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));
-                        });
+                    // 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]});
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS);
+
+                        // 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));
+                                blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                easyListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS) + " - " + getString(R.string.easylist));
+                            });
+                        }
 
                         // 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]};
                     }
                 }
 
                 // Check EasyPrivacy if it is enabled.
                 if (easyPrivacyEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        easyPrivacyBlockedRequests++;
+                    // Check the URL against EasyPrivacy.
+                    String[] easyPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
 
-                        // 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));
-                        });
+                    // 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[5],
+                                easyPrivacyResults[5]});
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS);
+
+                        // 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));
+                                blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                easyPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.easyprivacy));
+                            });
+                        }
 
                         // 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]};
                     }
                 }
 
                 // 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++;
+                    // Check the URL against Fanboy's Annoyance List.
+                    String[] fanboysAnnoyanceListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
 
-                        // 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));
-                        });
+                    // 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]});
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS);
+
+                        // 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));
+                                blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                fanboysAnnoyanceListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS) + " - " +
+                                        getString(R.string.fanboys_annoyance_list));
+                            });
+                        }
 
                         // 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 (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++;
+                    // Check the URL against Fanboy's Annoyance List.
+                    String[] fanboysSocialListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
 
-                        // 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));
-                        });
+                    // 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]});
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS);
+
+                        // 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));
+                                blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                fanboysSocialBlockingListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS) + " - " +
+                                        getString(R.string.fanboys_social_blocking_list));
+                            });
+                        }
 
                         // 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]};
                     }
                 }
 
                 // 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);
+                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.
-                    resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
+                    nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_DEFAULT, url});
                 }
 
                 // The resource request has not been blocked.  `return null` loads the requested resource.
@@ -5142,16 +5134,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 currentHostIpAddresses = "";
 
                 // Reset the list of resource requests.
-                resourceRequests.clear();
+                nestedScrollWebView.clearResourceRequests();
 
                 // Initialize the counters for requests blocked by each blocklist.
-                blockedRequests = 0;
-                easyListBlockedRequests = 0;
-                easyPrivacyBlockedRequests = 0;
-                fanboysAnnoyanceListBlockedRequests = 0;
-                fanboysSocialBlockingListBlockedRequests = 0;
-                ultraPrivacyBlockedRequests = 0;
-                thirdPartyBlockedRequests = 0;
+                nestedScrollWebView.resetRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                nestedScrollWebView.resetRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS);
+                nestedScrollWebView.resetRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS);
+                nestedScrollWebView.resetRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS);
+                nestedScrollWebView.resetRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS);
+                nestedScrollWebView.resetRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS);
+                nestedScrollWebView.resetRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS);
 
                 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
                 if (nightMode) {
@@ -5167,7 +5159,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     formattedUrlString = url;
 
                     // Display the formatted URL text.
-                    urlTextBox.setText(formattedUrlString);
+                    urlEditText.setText(formattedUrlString);
 
                     // Apply text highlighting to `urlTextBox`.
                     highlightUrlText();
@@ -5176,7 +5168,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     Uri currentUri = Uri.parse(formattedUrlString);
 
                     // Get the IP addresses for the host.
-                    new GetHostIpAddresses(activity).execute(currentUri.getHost());
+                    new GetHostIpAddresses(activity, currentWebView.getDomainSettingsDatabaseId()).execute(currentUri.getHost());
 
                     // Apply any custom domain settings if the URL was loaded by navigating history.
                     if (navigatingHistory) {
@@ -5267,13 +5259,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Set `formattedUrlString` to `""`.
                         formattedUrlString = "";
 
-                        urlTextBox.setText(formattedUrlString);
+                        urlEditText.setText(formattedUrlString);
 
                         // Request focus for `urlTextBox`.
-                        urlTextBox.requestFocus();
+                        urlEditText.requestFocus();
 
                         // Display the keyboard.
-                        inputMethodManager.showSoftInput(urlTextBox, 0);
+                        inputMethodManager.showSoftInput(urlEditText, 0);
 
                         // Apply the domain settings.  This clears any settings from the previous domain.
                         applyDomainSettings(formattedUrlString, true, false);
@@ -5282,9 +5274,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         formattedUrlString = nestedScrollWebView.getUrl();
 
                         // Only update the URL text box if the user is not typing in it.
-                        if (!urlTextBox.hasFocus()) {
+                        if (!urlEditText.hasFocus()) {
                             // Display the formatted URL text.
-                            urlTextBox.setText(formattedUrlString);
+                            urlEditText.setText(formattedUrlString);
 
                             // Apply text highlighting to `urlTextBox`.
                             highlightUrlText();
@@ -5296,7 +5288,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
                     if (!gettingIpAddresses) {
-                        checkPinnedMismatch();
+                        checkPinnedMismatch(currentWebView.getDomainSettingsDatabaseId());
                     }
                 }
 
@@ -5340,8 +5332,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
-        // Check to see if this is the first tab.
-        if (tabNumber == 0) {
+        // Check to see if this is the first page.
+        if (pageNumber == 0) {
             // Set this nested scroll WebView as the current WebView.
             currentWebView = nestedScrollWebView;