]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Disable screen timeout while playing fullscreen videos. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index b0cd103a68203752c29cafd546a9341d2ddd3725..cf19d3a18480c1963f87ecf20c0a0ca528fa40ac 100644 (file)
@@ -24,6 +24,7 @@ package com.stoutner.privacybrowser.activities;
 import android.Manifest;
 import android.annotation.SuppressLint;
 import android.app.Activity;
+import android.app.Dialog;
 import android.app.DownloadManager;
 import android.app.SearchManager;
 import android.content.ActivityNotFoundException;
@@ -49,6 +50,7 @@ import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.Message;
 import android.preference.PreferenceManager;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintManager;
@@ -71,6 +73,7 @@ import android.webkit.CookieManager;
 import android.webkit.HttpAuthHandler;
 import android.webkit.SslErrorHandler;
 import android.webkit.ValueCallback;
+import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
 import android.webkit.WebResourceResponse;
 import android.webkit.WebSettings;
@@ -115,6 +118,8 @@ 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.asynctasks.PopulateBlocklists;
+import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
@@ -125,15 +130,19 @@ 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.SaveWebpageImageDialog;
 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
+import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
 import com.stoutner.privacybrowser.helpers.AdHelper;
-import com.stoutner.privacybrowser.helpers.BlockListHelper;
+import com.stoutner.privacybrowser.helpers.BlocklistHelper;
 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
+import com.stoutner.privacybrowser.helpers.FileNameHelper;
 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
 import com.stoutner.privacybrowser.views.NestedScrollWebView;
 
@@ -157,7 +166,8 @@ import java.util.Set;
 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
-        EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener {
+        EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageImageDialog.SaveWebpageImageListener,
+        StoragePermissionDialog.StoragePermissionDialogListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
 
     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
     public static String orbotStatus;
@@ -184,6 +194,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
 
+    // Start activity for result request codes.
+    private final int FILE_UPLOAD_REQUEST_CODE = 0;
+    public final static int BROWSE_SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 1;
 
 
     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
@@ -199,11 +212,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
     private Menu optionsMenu;
 
-    // The blocklists are populated in `onCreate()` and accessed from `initializeWebView()`.
+    // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
     private ArrayList<List<String[]>> easyList;
     private ArrayList<List<String[]>> easyPrivacy;
     private ArrayList<List<String[]>> fanboysAnnoyanceList;
     private ArrayList<List<String[]>> fanboysSocialList;
+    private ArrayList<List<String[]>> ultraList;
     private ArrayList<List<String[]>> ultraPrivacy;
 
     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
@@ -292,14 +306,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
     private String downloadImageUrl;
 
-    // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
+    // The save website image file path string is used in `onSaveWebpageImage()` and `onRequestPermissionResult()`
+    private String saveWebsiteImageFilePath;
+
+    // The permission result request codes are used in `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, `onSaveWebpageImage()`,
+    // `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
     private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
+    private final int SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 3;
 
     @Override
     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
     @SuppressLint("ClickableViewAccessibility")
     protected void onCreate(Bundle savedInstanceState) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            WebView.enableSlowWholeDocumentDraw();
+        }
+
+        // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
+        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
@@ -325,14 +351,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the content view.
         setContentView(R.layout.main_framelayout);
 
-        // Get a handle for the input method.
-        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-
-        // Remove the lint warning below that the input method manager might be null.
-        assert inputMethodManager != null;
-
-        // Get a handle for the toolbar.
+        // Get handles for the views that need to be modified.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
         Toolbar toolbar = findViewById(R.id.toolbar);
+        ViewPager webViewPager = findViewById(R.id.webviewpager);
 
         // Set the action bar.  `SupportActionBar` must be used until the minimum API is >= 21.
         setSupportActionBar(toolbar);
@@ -347,2869 +369,3361 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         actionBar.setCustomView(R.layout.url_app_bar);
         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
-        // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
-        redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
-        initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
-        finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+        // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
+        drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
 
-        // Get handles for the URL views.
-        EditText urlEditText = findViewById(R.id.url_edittext);
-
-        // Remove the formatting from `urlTextBar` when the user is editing the text.
-        urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
-            if (hasFocus) {  // The user is editing the URL text box.
-                // Remove the highlighting.
-                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.
-                urlEditText.setSelection(0);
+        // Create the hamburger icon at the start of the AppBar.
+        actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
 
-                // Reapply the highlighting.
-                highlightUrlText();
-            }
-        });
+        // Initialize the web view pager adapter.
+        webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
 
-        // Set the go button on the keyboard to load the URL in `urlTextBox`.
-        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.
-                loadUrlFromTextBox();
+        // Set the pager adapter on the web view pager.
+        webViewPager.setAdapter(webViewPagerAdapter);
 
-                // If the enter key was pressed, consume the event.
-                return true;
-            } else {
-                // If any other key was pressed, do not consume the event.
-                return false;
-            }
-        });
+        // Store up to 100 tabs in memory.
+        webViewPager.setOffscreenPageLimit(100);
 
-        // Initialize the Orbot status and the waiting for Orbot trackers.
-        orbotStatus = "unknown";
-        waitingForOrbot = false;
+        // Populate the blocklists.
+        new PopulateBlocklists(this, this).execute();
+    }
 
-        // Create an Orbot status `BroadcastReceiver`.
-        orbotStatusBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                // Store the content of the status message in `orbotStatus`.
-                orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
+    @Override
+    protected void onNewIntent(Intent intent) {
+        // Replace the intent that started the app with this one.
+        setIntent(intent);
 
-                // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
-                if (orbotStatus.equals("ON") && waitingForOrbot) {
-                    // Reset the waiting for Orbot status.
-                    waitingForOrbot = false;
+        // Process the intent here if Privacy Browser is fully initialized.  If the process has been killed by the system while sitting in the background, this will be handled in `initializeWebView()`.
+        if (ultraPrivacy != null) {
+            // Get the information from the intent.
+            String intentAction = intent.getAction();
+            Uri intentUriData = intent.getData();
 
-                    // Get the intent that started the app.
-                    Intent launchingIntent = getIntent();
+            // Determine if this is a web search.
+            boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
 
-                    // Get the information from the intent.
-                    String launchingIntentAction = launchingIntent.getAction();
-                    Uri launchingIntentUriData = launchingIntent.getData();
+            // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
+            if (intentUriData != null || isWebSearch) {
+                // Get the shared preferences.
+                SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                    // If the intent action is a web search, perform the search.
-                    if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
-                        // Create an encoded URL string.
-                        String encodedUrlString;
+                // Create a URL string.
+                String url;
 
-                        // Sanitize the search input and convert it to a search.
-                        try {
-                            encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
-                        } catch (UnsupportedEncodingException exception) {
-                            encodedUrlString = "";
-                        }
+                // If the intent action is a web search, perform the search.
+                if (isWebSearch) {
+                    // Create an encoded URL string.
+                    String encodedUrlString;
 
-                        // Load the completed search URL.
-                        loadUrl(searchURL + encodedUrlString);
-                    } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
-                        // Load the URL from the intent.
-                        loadUrl(launchingIntentUriData.toString());
-                    } else {  // The is no URL in the intent.
-                        // Select the homepage based on the proxy through Orbot status.
-                        if (proxyThroughOrbot) {
-                            // Load the Tor homepage.
-                            loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
-                        } else {
-                            // Load the normal homepage.
-                            loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
-                        }
+                    // Sanitize the search input and convert it to a search.
+                    try {
+                        encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+                    } catch (UnsupportedEncodingException exception) {
+                        encodedUrlString = "";
                     }
+
+                    // Add the base search URL.
+                    url = searchURL + encodedUrlString;
+                } else {  // The intent should contain a URL.
+                    // Set the intent data as the URL.
+                    url = intentUriData.toString();
                 }
-            }
-        };
 
-        // Register `orbotStatusBroadcastReceiver` on `this` context.
-        this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
+                // Add a new tab if specified in the preferences.
+                if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
+                    // Set the loading new intent flag.
+                    loadingNewIntent = true;
 
-        // Instantiate the blocklist helper.
-        BlockListHelper blockListHelper = new BlockListHelper();
+                    // Add a new tab.
+                    addNewTab(url, true);
+                } else {  // Load the URL in the current tab.
+                    // Make it so.
+                    loadUrl(url);
+                }
 
-        // Parse the block lists.
-        easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
-        easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
-        fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
-        fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
-        ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
+                // Get a handle for the drawer layout.
+                DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-        // Get handles for views that need to be modified.
-        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-        NavigationView navigationView = findViewById(R.id.navigationview);
-        TabLayout tabLayout = findViewById(R.id.tablayout);
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-        ViewPager webViewPager = findViewById(R.id.webviewpager);
-        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
-        FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
-        FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
-        FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
-        EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+                // Close the navigation drawer if it is open.
+                if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
+                    drawerLayout.closeDrawer(GravityCompat.START);
+                }
 
-        // Listen for touches on the navigation menu.
-        navigationView.setNavigationItemSelectedListener(this);
+                // Close the bookmarks drawer if it is open.
+                if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
+                    drawerLayout.closeDrawer(GravityCompat.END);
+                }
+            }
+        }
+    }
 
-        // Get handles for the navigation menu and the back and forward menu items.  The menu is zero-based.
-        Menu navigationMenu = navigationView.getMenu();
-        MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
-        MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
-        MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
-        MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5);
+    @Override
+    public void onRestart() {
+        // Run the default commands.
+        super.onRestart();
 
-        // Initialize the web view pager adapter.
-        webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
+        // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
+        if (proxyThroughOrbot) {
+            // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
+            Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
 
-        // Set the pager adapter on the web view pager.
-        webViewPager.setAdapter(webViewPagerAdapter);
+            // Send the intent to the Orbot package.
+            orbotIntent.setPackage("org.torproject.android");
 
-        // Store up to 100 tabs in memory.
-        webViewPager.setOffscreenPageLimit(100);
+            // Make it so.
+            sendBroadcast(orbotIntent);
+        }
 
-        // Update the web view pager every time a tab is modified.
-        webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
-            @Override
-            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-                // Do nothing.
-            }
+        // Apply the app settings if returning from the Settings activity.
+        if (reapplyAppSettingsOnRestart) {
+            // Reset the reapply app settings on restart tracker.
+            reapplyAppSettingsOnRestart = false;
 
-            @Override
-            public void onPageSelected(int position) {
-                // Close the find on page bar if it is open.
-                closeFindOnPage(null);
+            // Apply the app settings.
+            applyAppSettings();
+        }
 
-                // Set the current WebView.
-                setCurrentWebView(position);
+        // Apply the domain settings if returning from the settings or domains activity.
+        if (reapplyDomainSettingsOnRestart) {
+            // Reset the reapply domain settings on restart tracker.
+            reapplyDomainSettingsOnRestart = false;
 
-                // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled via swiping in the view pager or by creating a new tab.
-                if (tabLayout.getSelectedTabPosition() != position) {
-                    // Create a handler to select the tab.
-                    Handler selectTabHandler = new Handler();
+            // Reapply the domain settings for each tab.
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-                    // Create a runnable select the new tab.
-                    Runnable selectTabRunnable = () -> {
-                        // Get a handle for the tab.
-                        TabLayout.Tab tab = tabLayout.getTabAt(position);
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-                        // Assert that the tab is not null.
-                        assert tab != null;
+                // 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);
 
-                        // Select the tab.
-                        tab.select();
-                    };
+                    // Reset the current domain name so the domain settings will be reapplied.
+                    nestedScrollWebView.resetCurrentDomainName();
 
-                    // Select the tab layout after 100 milliseconds, which leaves enough time for a new tab to be created.
-                    selectTabHandler.postDelayed(selectTabRunnable, 100);
+                    // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
+                    if (nestedScrollWebView.getUrl() != null) {
+                        applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
+                    }
                 }
             }
+        }
 
-            @Override
-            public void onPageScrollStateChanged(int state) {
-                // Do nothing.
-            }
-        });
+        // Load the URL on restart (used when loading a bookmark).
+        if (loadUrlOnRestart) {
+            // Load the specified URL.
+            loadUrl(urlToLoadOnRestart);
 
-        // Display the View SSL Certificate dialog when the currently selected tab is reselected.
-        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
-            @Override
-            public void onTabSelected(TabLayout.Tab tab) {
-                // Select the same page in the view pager.
-                webViewPager.setCurrentItem(tab.getPosition());
-            }
+            // Reset the load on restart tracker.
+            loadUrlOnRestart = false;
+        }
 
-            @Override
-            public void onTabUnselected(TabLayout.Tab tab) {
-                // Do nothing.
-            }
+        // Update the bookmarks drawer if returning from the Bookmarks activity.
+        if (restartFromBookmarksActivity) {
+            // Get a handle for the drawer layout.
+            DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-            @Override
-            public void onTabReselected(TabLayout.Tab tab) {
-                // Instantiate the View SSL Certificate dialog.
-                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
+            // Close the bookmarks drawer.
+            drawerLayout.closeDrawer(GravityCompat.END);
 
-                // Display the View SSL Certificate dialog.
-                viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
-            }
-        });
+            // Reload the bookmarks drawer.
+            loadBookmarksFolder();
 
-        // Add the first tab.
-        addTab(null);
+            // Reset `restartFromBookmarksActivity`.
+            restartFromBookmarksActivity = false;
+        }
 
-        // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
-        // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
-        if (darkTheme) {
-            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
-            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
-            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
-            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
-        } else {
-            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
-            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
-            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
-            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
-        }
-
-        // Set the launch bookmarks activity FAB to launch the bookmarks activity.
-        launchBookmarksActivityFab.setOnClickListener(v -> {
-            // Get a copy of the favorite icon bitmap.
-            Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
+        // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
+        updatePrivacyIcons(true);
+    }
 
-            // Create a favorite icon byte array output stream.
-            ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
+    @Override
+    public void onResume() {
+        // Run the default commands.
+        super.onResume();
 
-            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Convert the favorite icon byte array stream to a byte array.
-            byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
 
-            // Create an intent to launch the bookmarks activity.
-            Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
+            // 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);
 
-            // Add the extra information to the intent.
-            bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
-            bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
-            bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
-            bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
+                // Resume the nested scroll WebView JavaScript timers.
+                nestedScrollWebView.resumeTimers();
 
-            // Make it so.
-            startActivity(bookmarksIntent);
-        });
+                // Resume the nested scroll WebView.
+                nestedScrollWebView.onResume();
+            }
+        }
 
-        // Set the create new bookmark folder FAB to display an alert dialog.
-        createBookmarkFolderFab.setOnClickListener(v -> {
-            // Create a create bookmark folder dialog.
-            DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
+        // Display a message to the user if waiting for Orbot.
+        if (waitingForOrbot && !orbotStatus.equals("ON")) {
+            // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+            currentWebView.getSettings().setUseWideViewPort(false);
 
-            // Show the create bookmark folder dialog.
-            createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
-        });
+            // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
+            currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
+        }
 
-        // Set the create new bookmark FAB to display an alert dialog.
-        createBookmarkFab.setOnClickListener(view -> {
-            // Instantiate the create bookmark dialog.
-            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
+        if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
+            // Get a handle for the root frame layouts.
+            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
 
-            // Display the create bookmark dialog.
-            createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
-        });
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-        // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
-        findOnPageEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-                // Do nothing.
-            }
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // Resume the adView for the free flavor.
+            // Resume the ad.
+            AdHelper.resumeAd(findViewById(R.id.adview));
+        }
+    }
 
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                // Do nothing.
-            }
+    @Override
+    public void onPause() {
+        // Run the default commands.
+        super.onPause();
 
-            @Override
-            public void afterTextChanged(Editable s) {
-                // Search for the text in the WebView if it is not null.  Sometimes on resume after a period of non-use the WebView will be null.
-                if (currentWebView != null) {
-                    currentWebView.findAllAsync(findOnPageEditText.getText().toString());
-                }
-            }
-        });
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-        // 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.
-                inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
 
-                // Consume the event.
-                return true;
-            } else {  // A different key was pressed.
-                // Do not consume the event.
-                return false;
-            }
-        });
+            // 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);
 
-        // Implement swipe to refresh.
-        swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
+                // Pause the nested scroll WebView.
+                nestedScrollWebView.onPause();
 
-        // Store the default progress view offsets for use later in `initializeWebView()`.
-        defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
-        defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
+                // Pause the nested scroll WebView JavaScript timers.
+                nestedScrollWebView.pauseTimers();
+            }
+        }
 
-        // Set the swipe to refresh color according to the theme.
-        if (darkTheme) {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
-            swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
-        } else {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
+        // Pause the ad or it will continue to consume resources in the background on the free flavor.
+        if (BuildConfig.FLAVOR.contentEquals("free")) {
+            // Pause the ad.
+            AdHelper.pauseAd(findViewById(R.id.adview));
         }
+    }
 
-        // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
-        drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
-        drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
+    @Override
+    public void onDestroy() {
+        // Unregister the Orbot status broadcast receiver.
+        this.unregisterReceiver(orbotStatusBroadcastReceiver);
 
-        // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
-        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
+        // Close the bookmarks cursor and database.
+        bookmarksCursor.close();
+        bookmarksDatabaseHelper.close();
 
-        // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
-        currentBookmarksFolder = "";
+        // Run the default commands.
+        super.onDestroy();
+    }
 
-        // Load the home folder, which is `""` in the database.
-        loadBookmarksFolder();
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu.  This adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.webview_options_menu, menu);
 
-        bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
-            // Convert the id from long to int to match the format of the bookmarks database.
-            int databaseID = (int) id;
+        // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
+        optionsMenu = menu;
 
-            // Get the bookmark cursor for this ID and move it to the first row.
-            Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
-            bookmarkCursor.moveToFirst();
+        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
+        updatePrivacyIcons(false);
 
-            // Act upon the bookmark according to the type.
-            if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
-                // Store the new folder name in `currentBookmarksFolder`.
-                currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+        // Get handles for the menu items.
+        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
+        MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
 
-                // Load the new folder.
-                loadBookmarksFolder();
-            } else {  // The selected bookmark is not a folder.
-                // Load the bookmark URL.
-                loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
+        // Only display third-party cookies if API >= 21
+        toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
 
-                // Close the bookmarks drawer.
-                drawerLayout.closeDrawer(GravityCompat.END);
-            }
+        // Only display the form data menu items if the API < 26.
+        toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+        clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
 
-            // Close the `Cursor`.
-            bookmarkCursor.close();
-        });
+        // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
+        clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
 
-        bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
-            // Convert the database ID from `long` to `int`.
-            int databaseId = (int) id;
+        // Only show Ad Consent if this is the free flavor.
+        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
 
-            // Find out if the selected bookmark is a folder.
-            boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
+        // Get the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            if (isFolder) {
-                // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
-                oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+        // Get the dark theme and app bar preferences..
+        boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-                // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
-                DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
-                editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
-            } else {
-                // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
-                DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
-                editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark));
-            }
+        // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
+        if (displayAdditionalAppBarIcons) {
+            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        } else { //Do not display the additional icons.
+            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+        }
 
-            // Consume the event.
-            return true;
-        });
+        // Replace Refresh with Stop if a URL is already loading.
+        if (currentWebView != null && currentWebView.getProgress() != 100) {
+            // Set the title.
+            refreshMenuItem.setTitle(R.string.stop);
 
-        // Get the status bar pixel size.
-        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
-        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
+            // If the icon is displayed in the AppBar, set it according to the theme.
+            if (displayAdditionalAppBarIcons) {
+                if (darkTheme) {
+                    refreshMenuItem.setIcon(R.drawable.close_dark);
+                } else {
+                    refreshMenuItem.setIcon(R.drawable.close_light);
+                }
+            }
+        }
 
-        // Get the resource density.
-        float screenDensity = getResources().getDisplayMetrics().density;
+        // Done.
+        return true;
+    }
 
-        // Calculate the drawer header padding.  This is used to move the text in the drawer headers below any cutouts.
-        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
-        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
-        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // Get handles for the menu items.
+        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
+        MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+        MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+        MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+        MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
+        MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
+        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
+        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
+        MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
+        MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
+        MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
+        MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
+        MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist);
+        MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
+        MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+        MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
+        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
+        MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
+        MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
+        MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
+        MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
 
-        // The drawer listener is used to update the navigation menu.`
-        drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
-            @Override
-            public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
-            }
+        // Get a handle for the cookie manager.
+        CookieManager cookieManager = CookieManager.getInstance();
 
-            @Override
-            public void onDrawerOpened(@NonNull View drawerView) {
-            }
+        // Initialize the current user agent string and the font size.
+        String currentUserAgent = getString(R.string.user_agent_privacy_browser);
+        int fontSize = 100;
 
-            @Override
-            public void onDrawerClosed(@NonNull View drawerView) {
+        // 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);
             }
 
-            @Override
-            public void onDrawerStateChanged(int newState) {
-                if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) {  // A drawer is opening or closing.
-                    // Get handles for the drawer headers.
-                    TextView navigationHeaderTextView = findViewById(R.id.navigationText);
-                    TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
+            // Get the current user agent from the WebView.
+            currentUserAgent = currentWebView.getSettings().getUserAgentString();
 
-                    // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
-                    if (navigationHeaderTextView != null) {
-                        navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
-                    }
+            // Get the current font size from the
+            fontSize = currentWebView.getSettings().getTextZoom();
 
-                    // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
-                    if (bookmarksHeaderTextView != null) {
-                        bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
-                    }
+            // Set the status of the menu item checkboxes.
+            domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
+            saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
+            easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+            easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+            fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+            fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+            ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+            ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+            blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+            swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
+            wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
+            displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
+            nightModeMenuItem.setChecked(currentWebView.getNightMode());
 
-                    // Update the navigation menu items.
-                    navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
-                    navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
-                    navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
-                    navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+            // 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.EASYLIST) + " - " + getString(R.string.easylist));
+            easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
+            fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
+            fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
+            ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
+            ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
+            blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
 
-                    // Hide the keyboard (if displayed).
-                    inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+            // Only modify third-party cookies if the API >= 21.
+            if (Build.VERSION.SDK_INT >= 21) {
+                // Set the status of the third-party cookies checkbox.
+                thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
 
-                    // 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.
-                    urlEditText.clearFocus();
-                    currentWebView.clearFocus();
-                }
+                // Enable third-party cookies if first-party cookies are enabled.
+                thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
             }
-        });
 
-        // Create the hamburger icon at the start of the AppBar.
-        actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
+            // Enable DOM Storage if JavaScript is enabled.
+            domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
+        }
 
-        // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
-        customHeaders.put("X-Requested-With", "");
+        // Set the status of the menu item checkboxes.
+        firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
+        proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
 
-        // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
-        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+        // Enable Clear Cookies if there are any.
+        clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
 
-        // 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 the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+        String privateDataDirectoryString = getApplicationInfo().dataDir;
 
-        // Get a handle for the WebView.
-        WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
+        // Get a count of the number of files in the Local Storage directory.
+        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
+        int localStorageDirectoryNumberOfFiles = 0;
+        if (localStorageDirectory.exists()) {
+            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
+        }
 
-        // Store the default user agent.
-        webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
+        // Get a count of the number of files in the IndexedDB directory.
+        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
+        int indexedDBDirectoryNumberOfFiles = 0;
+        if (indexedDBDirectory.exists()) {
+            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
+        }
 
-        // Destroy the bare WebView.
-        bareWebView.destroy();
-    }
+        // Enable Clear DOM Storage if there is any.
+        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
 
-    @Override
-    protected void onNewIntent(Intent intent) {
-        // Get the information from the intent.
-        String intentAction = intent.getAction();
-        Uri intentUriData = intent.getData();
+        // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
+        if (Build.VERSION.SDK_INT < 26) {
+            // Get the WebView database.
+            WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
 
-        // Determine if this is a web search.
-        boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
+            // Enable the clear form data menu item if there is anything to clear.
+            clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
+        }
 
-        // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
-        if (intentUriData != null || isWebSearch) {
-            // Get the shared preferences.
-            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        // Enable Clear Data if any of the submenu items are enabled.
+        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
 
-            // Create a URL string.
-            String url;
+        // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
+        fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
 
-            // If the intent action is a web search, perform the search.
-            if (isWebSearch) {
-                // Create an encoded URL string.
-                String encodedUrlString;
+        // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
+        if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
+            menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
+        } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
+            menu.findItem(R.id.user_agent_webview_default).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
+            menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
+            menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
+            menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
+            menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
+            menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
+            menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
+            menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
+            menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
+            menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
+            menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
+        } else {  // Custom user agent.
+            menu.findItem(R.id.user_agent_custom).setChecked(true);
+        }
 
-                // Sanitize the search input and convert it to a search.
-                try {
-                    encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
-                } catch (UnsupportedEncodingException exception) {
-                    encodedUrlString = "";
-                }
+        // Instantiate the font size title and the selected font size menu item.
+        String fontSizeTitle;
+        MenuItem selectedFontSizeMenuItem;
 
-                // Add the base search URL.
-                url = searchURL + encodedUrlString;
-            } else {  // The intent should contain a URL.
-                // Set the intent data as the URL.
-                url = intentUriData.toString();
-            }
+        // Prepare the font size title and current size menu item.
+        switch (fontSize) {
+            case 25:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
+                break;
 
-            // Add a new tab if specified in the preferences.
-            if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
-                // Set the loading new intent flag.
-                loadingNewIntent = true;
+            case 50:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
+                break;
 
-                // Add a new tab.
-                addNewTab(url);
-            } else {  // Load the URL in the current tab.
-                // Make it so.
-                loadUrl(url);
-            }
+            case 75:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
+                break;
 
-            // Get a handle for the drawer layout.
-            DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+            case 100:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+                break;
 
-            // Close the navigation drawer if it is open.
-            if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
-                drawerLayout.closeDrawer(GravityCompat.START);
-            }
+            case 125:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
+                break;
 
-            // Close the bookmarks drawer if it is open.
-            if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
-                drawerLayout.closeDrawer(GravityCompat.END);
-            }
-        }
-    }
+            case 150:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
+                break;
 
-    @Override
-    public void onRestart() {
-        // Run the default commands.
-        super.onRestart();
+            case 175:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
+                break;
 
-        // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
-        if (proxyThroughOrbot) {
-            // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
-            Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
+            case 200:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
+                break;
 
-            // Send the intent to the Orbot package.
-            orbotIntent.setPackage("org.torproject.android");
-
-            // Make it so.
-            sendBroadcast(orbotIntent);
+            default:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+                break;
         }
 
-        // Apply the app settings if returning from the Settings activity.
-        if (reapplyAppSettingsOnRestart) {
-            // Reset the reapply app settings on restart tracker.
-            reapplyAppSettingsOnRestart = false;
+        // Set the font size title and select the current size menu item.
+        fontSizeMenuItem.setTitle(fontSizeTitle);
+        selectedFontSizeMenuItem.setChecked(true);
 
-            // Apply the app settings.
-            applyAppSettings();
-        }
+        // Run all the other default commands.
+        super.onPrepareOptionsMenu(menu);
 
-        // Apply the domain settings if returning from the settings or domains activity.
-        if (reapplyDomainSettingsOnRestart) {
-            // Reset the reapply domain settings on restart tracker.
-            reapplyDomainSettingsOnRestart = false;
+        // Display the menu.
+        return true;
+    }
 
-            // Reapply the domain settings for each tab.
-            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-                // Get the WebView tab fragment.
-                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+    @Override
+    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
+    @SuppressLint("SetJavaScriptEnabled")
+    public boolean onOptionsItemSelected(MenuItem menuItem) {
+        // Get the selected menu item ID.
+        int menuItemId = menuItem.getItemId();
 
-                // Get the fragment view.
-                View fragmentView = webViewTabFragment.getView();
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                // 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);
+        // Get a handle for the cookie manager.
+        CookieManager cookieManager = CookieManager.getInstance();
 
-                    // Reset the current domain name so the domain settings will be reapplied.
-                    nestedScrollWebView.resetCurrentDomainName();
+        // Run the commands that correlate to the selected menu item.
+        switch (menuItemId) {
+            case R.id.toggle_javascript:
+                // Toggle the JavaScript status.
+                currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
 
-                    // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
-                    if (nestedScrollWebView.getUrl() != null) {
-                        applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
-                    }
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
+
+                // Display a `Snackbar`.
+                if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
+                } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
+                } else {  // Privacy mode.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                 }
-            }
-        }
 
-        // Load the URL on restart (used when loading a bookmark).
-        if (loadUrlOnRestart) {
-            // Load the specified URL.
-            loadUrl(urlToLoadOnRestart);
+                // Reload the current WebView.
+                currentWebView.reload();
 
-            // Reset the load on restart tracker.
-            loadUrlOnRestart = false;
-        }
+                // Consume the event.
+                return true;
 
-        // Update the bookmarks drawer if returning from the Bookmarks activity.
-        if (restartFromBookmarksActivity) {
-            // Get a handle for the drawer layout.
-            DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+            case R.id.add_or_edit_domain:
+                if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
+                    // Reapply the domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
 
-            // Close the bookmarks drawer.
-            drawerLayout.closeDrawer(GravityCompat.END);
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
-            // Reload the bookmarks drawer.
-            loadBookmarksFolder();
+                    // Add the extra information to the intent.
+                    domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
+                    domainsIntent.putExtra("close_on_back", true);
+                    domainsIntent.putExtra("current_url", currentWebView.getUrl());
 
-            // Reset `restartFromBookmarksActivity`.
-            restartFromBookmarksActivity = false;
-        }
+                    // Get the current certificate.
+                    SslCertificate sslCertificate = currentWebView.getCertificate();
 
-        // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
-        updatePrivacyIcons(true);
-    }
+                    // Check to see if the SSL certificate is populated.
+                    if (sslCertificate != null) {
+                        // Extract the certificate to strings.
+                        String issuedToCName = sslCertificate.getIssuedTo().getCName();
+                        String issuedToOName = sslCertificate.getIssuedTo().getOName();
+                        String issuedToUName = sslCertificate.getIssuedTo().getUName();
+                        String issuedByCName = sslCertificate.getIssuedBy().getCName();
+                        String issuedByOName = sslCertificate.getIssuedBy().getOName();
+                        String issuedByUName = sslCertificate.getIssuedBy().getUName();
+                        long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+                        long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
 
-    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
-    @Override
-    public void onResume() {
-        // Run the default commands.
-        super.onResume();
+                        // Add the certificate to the intent.
+                        domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+                        domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+                        domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+                        domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+                        domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+                        domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+                        domainsIntent.putExtra("ssl_start_date", startDateLong);
+                        domainsIntent.putExtra("ssl_end_date", endDateLong);
+                    }
 
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+                    // Check to see if the current IP addresses have been received.
+                    if (currentWebView.hasCurrentIpAddresses()) {
+                        // Add the current IP addresses to the intent.
+                        domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                    }
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                    // Make it so.
+                    startActivity(domainsIntent);
+                } else {  // Add a new domain.
+                    // Apply the new domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
 
-            // 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);
+                    // Get the current domain
+                    Uri currentUri = Uri.parse(currentWebView.getUrl());
+                    String currentDomain = currentUri.getHost();
 
-                // Resume the nested scroll WebView JavaScript timers.
-                nestedScrollWebView.resumeTimers();
+                    // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+                    DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
 
-                // Resume the nested scroll WebView.
-                nestedScrollWebView.onResume();
-            }
-        }
+                    // Create the domain and store the database ID.
+                    int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
 
-        // Display a message to the user if waiting for Orbot.
-        if (waitingForOrbot && !orbotStatus.equals("ON")) {
-            // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
-            currentWebView.getSettings().setUseWideViewPort(false);
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
-            // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
-            currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
-        }
+                    // Add the extra information to the intent.
+                    domainsIntent.putExtra("load_domain", newDomainDatabaseId);
+                    domainsIntent.putExtra("close_on_back", true);
+                    domainsIntent.putExtra("current_url", currentWebView.getUrl());
 
-        if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
-            // Get a handle for the root frame layouts.
-            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+                    // Get the current certificate.
+                    SslCertificate sslCertificate = currentWebView.getCertificate();
 
-            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                    // Check to see if the SSL certificate is populated.
+                    if (sslCertificate != null) {
+                        // Extract the certificate to strings.
+                        String issuedToCName = sslCertificate.getIssuedTo().getCName();
+                        String issuedToOName = sslCertificate.getIssuedTo().getOName();
+                        String issuedToUName = sslCertificate.getIssuedTo().getUName();
+                        String issuedByCName = sslCertificate.getIssuedBy().getCName();
+                        String issuedByOName = sslCertificate.getIssuedBy().getOName();
+                        String issuedByUName = sslCertificate.getIssuedBy().getUName();
+                        long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+                        long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
 
-            /* Hide the system bars.
-             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-        } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // Resume the adView for the free flavor.
-            // Resume the ad.
-            AdHelper.resumeAd(findViewById(R.id.adview));
-        }
-    }
+                        // Add the certificate to the intent.
+                        domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+                        domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+                        domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+                        domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+                        domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+                        domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+                        domainsIntent.putExtra("ssl_start_date", startDateLong);
+                        domainsIntent.putExtra("ssl_end_date", endDateLong);
+                    }
 
-    @Override
-    public void onPause() {
-        // Run the default commands.
-        super.onPause();
+                    // Check to see if the current IP addresses have been received.
+                    if (currentWebView.hasCurrentIpAddresses()) {
+                        // Add the current IP addresses to the intent.
+                        domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                    }
 
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+                    // Make it so.
+                    startActivity(domainsIntent);
+                }
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                // Consume the event.
+                return true;
 
-            // 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);
+            case R.id.toggle_first_party_cookies:
+                // Switch the first-party cookie status.
+                cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
 
-                // Pause the nested scroll WebView.
-                nestedScrollWebView.onPause();
+                // Store the first-party cookie status.
+                currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
 
-                // Pause the nested scroll WebView JavaScript timers.
-                nestedScrollWebView.pauseTimers();
-            }
-        }
+                // Update the menu checkbox.
+                menuItem.setChecked(cookieManager.acceptCookie());
 
-        // Pause the ad or it will continue to consume resources in the background on the free flavor.
-        if (BuildConfig.FLAVOR.contentEquals("free")) {
-            // Pause the ad.
-            AdHelper.pauseAd(findViewById(R.id.adview));
-        }
-    }
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
 
-    @Override
-    public void onDestroy() {
-        // Unregister the Orbot status broadcast receiver.
-        this.unregisterReceiver(orbotStatusBroadcastReceiver);
+                // Display a snackbar.
+                if (cookieManager.acceptCookie()) {  // First-party cookies are enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+                } else {  // Privacy mode.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
+                }
 
-        // Close the bookmarks cursor and database.
-        bookmarksCursor.close();
-        bookmarksDatabaseHelper.close();
+                // Reload the current WebView.
+                currentWebView.reload();
 
-        // Run the default commands.
-        super.onDestroy();
-    }
+                // Consume the event.
+                return true;
 
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        // Inflate the menu.  This adds items to the action bar if it is present.
-        getMenuInflater().inflate(R.menu.webview_options_menu, menu);
+            case R.id.toggle_third_party_cookies:
+                if (Build.VERSION.SDK_INT >= 21) {
+                    // Switch the status of thirdPartyCookiesEnabled.
+                    cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
 
-        // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
-        optionsMenu = menu;
+                    // Update the menu checkbox.
+                    menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
 
-        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
-        updatePrivacyIcons(false);
+                    // Display a snackbar.
+                    if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
+                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                    } else {
+                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+                    }
 
-        // Get handles for the menu items.
-        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
-        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
-        MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
+                    // Reload the current WebView.
+                    currentWebView.reload();
+                } // Else do nothing because SDK < 21.
 
-        // Only display third-party cookies if API >= 21
-        toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
+                // Consume the event.
+                return true;
 
-        // Only display the form data menu items if the API < 26.
-        toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
-        clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+            case R.id.toggle_dom_storage:
+                // Toggle the status of domStorageEnabled.
+                currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
 
-        // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
-        clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
 
-        // Only show Ad Consent if this is the free flavor.
-        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
+                // Update the privacy icon.  `true` refreshes the app bar icons.
+                updatePrivacyIcons(true);
 
-        // Get the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+                // Display a snackbar.
+                if (currentWebView.getSettings().getDomStorageEnabled()) {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
+                } else {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
+                }
 
-        // Get the dark theme and app bar preferences..
-        boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
-        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+                // Reload the current WebView.
+                currentWebView.reload();
 
-        // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
-        if (displayAdditionalAppBarIcons) {
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-        } else { //Do not display the additional icons.
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-        }
+                // Consume the event.
+                return true;
 
-        // Replace Refresh with Stop if a URL is already loading.
-        if (currentWebView != null && currentWebView.getProgress() != 100) {
-            // Set the title.
-            refreshMenuItem.setTitle(R.string.stop);
+            // Form data can be removed once the minimum API >= 26.
+            case R.id.toggle_save_form_data:
+                // Switch the status of saveFormDataEnabled.
+                currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
 
-            // If the icon is displayed in the AppBar, set it according to the theme.
-            if (displayAdditionalAppBarIcons) {
-                if (darkTheme) {
-                    refreshMenuItem.setIcon(R.drawable.close_dark);
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
+
+                // Display a snackbar.
+                if (currentWebView.getSettings().getSaveFormData()) {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
                 } else {
-                    refreshMenuItem.setIcon(R.drawable.close_light);
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
                 }
-            }
-        }
 
-        return true;
-    }
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
 
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        // Get handles for the menu items.
-        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
-        MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
-        MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
-        MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
-        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
-        MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
-        MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
-        MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
-        MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
-        MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
-        MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
-        MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
-        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
-        MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
-        MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
-        MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
-        MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.clear_cookies:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
+                                    // Delete the cookies, which command varies by SDK.
+                                    if (Build.VERSION.SDK_INT < 21) {
+                                        cookieManager.removeAllCookie();
+                                    } else {
+                                        cookieManager.removeAllCookies(null);
+                                    }
+                                }
+                            }
+                        })
+                        .show();
+
+                // Consume the event.
+                return true;
+
+            case R.id.clear_dom_storage:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
+                                    // Delete the DOM Storage.
+                                    WebStorage webStorage = WebStorage.getInstance();
+                                    webStorage.deleteAllData();
+
+                                    // Initialize a handler to manually delete the DOM storage files and directories.
+                                    Handler deleteDomStorageHandler = new Handler();
+
+                                    // Setup a runnable to manually delete the DOM storage files and directories.
+                                    Runnable deleteDomStorageRunnable = () -> {
+                                        try {
+                                            // Get a handle for the runtime.
+                                            Runtime runtime = Runtime.getRuntime();
+
+                                            // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
+                                            // which links to `/data/data/com.stoutner.privacybrowser.standard`.
+                                            String privateDataDirectoryString = getApplicationInfo().dataDir;
+
+                                            // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                                            Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+                                            // Multiple commands must be used because `Runtime.exec()` does not like `*`.
+                                            Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                                            Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+                                            Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+                                            Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+
+                                            // Wait for the processes to finish.
+                                            deleteLocalStorageProcess.waitFor();
+                                            deleteIndexProcess.waitFor();
+                                            deleteQuotaManagerProcess.waitFor();
+                                            deleteQuotaManagerJournalProcess.waitFor();
+                                            deleteDatabasesProcess.waitFor();
+                                        } catch (Exception exception) {
+                                            // Do nothing if an error is thrown.
+                                        }
+                                    };
+
+                                    // Manually delete the DOM storage files after 200 milliseconds.
+                                    deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
+                                }
+                            }
+                        })
+                        .show();
+
+                // Consume the event.
+                return true;
+
+            // Form data can be remove once the minimum API >= 26.
+            case R.id.clear_form_data:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
+                                    // Delete the form data.
+                                    WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
+                                    mainWebViewDatabase.clearFormData();
+                                }
+                            }
+                        })
+                        .show();
+
+                // Consume the event.
+                return true;
+
+            case R.id.easylist:
+                // Toggle the EasyList status.
+                currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.easyprivacy:
+                // Toggle the EasyPrivacy status.
+                currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.fanboys_annoyance_list:
+                // Toggle Fanboy's Annoyance List status.
+                currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+
+                // Update the staus of Fanboy's Social Blocking List.
+                MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
+                fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.fanboys_social_blocking_list:
+                // Toggle Fanboy's Social Blocking List status.
+                currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.ultralist:
+                // Toggle the UltraList status.
+                currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.ultraprivacy:
+                // Toggle the UltraPrivacy status.
+                currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.block_all_third_party_requests:
+                //Toggle the third-party requests blocker status.
+                currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+
+                // Update the menu checkbox.
+                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_privacy_browser:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_webview_default:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString("");
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_firefox_on_android:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_chrome_on_android:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_safari_on_ios:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_firefox_on_linux:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_chromium_on_linux:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_firefox_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_chrome_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_edge_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_internet_explorer_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_safari_on_macos:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.user_agent_custom:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+
+                // Reload the current WebView.
+                currentWebView.reload();
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_twenty_five_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(25);
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_fifty_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(50);
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_seventy_five_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(75);
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_one_hundred_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(100);
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_one_hundred_twenty_five_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(125);
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_one_hundred_fifty_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(150);
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_one_hundred_seventy_five_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(175);
+
+                // Consume the event.
+                return true;
+
+            case R.id.font_size_two_hundred_percent:
+                // Set the font size.
+                currentWebView.getSettings().setTextZoom(200);
+
+                // Consume the event.
+                return true;
+
+            case R.id.swipe_to_refresh:
+                // Toggle the stored status of swipe to refresh.
+                currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
+
+                // Get a handle for the swipe refresh layout.
+                SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
+                // Update the swipe refresh layout.
+                if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
+                    // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
+                    swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                } else {  // Swipe to refresh is disabled.
+                    // Disable the swipe refresh layout.
+                    swipeRefreshLayout.setEnabled(false);
+                }
+
+                // Consume the event.
+                return true;
+
+            case R.id.wide_viewport:
+                // Toggle the viewport.
+                currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
+
+                // Consume the event.
+                return true;
+
+            case R.id.display_images:
+                if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
+                    // Disable loading of images.
+                    currentWebView.getSettings().setLoadsImagesAutomatically(false);
+
+                    // Reload the website to remove existing images.
+                    currentWebView.reload();
+                } else {  // Images are not currently loaded automatically.
+                    // Enable loading of images.  Missing images will be loaded without the need for a reload.
+                    currentWebView.getSettings().setLoadsImagesAutomatically(true);
+                }
+
+                // Consume the event.
+                return true;
+
+            case R.id.night_mode:
+                // Toggle night mode.
+                currentWebView.setNightMode(!currentWebView.getNightMode());
+
+                // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
+                if (currentWebView.getNightMode()) {  // Night mode is enabled, which requires JavaScript.
+                    // Enable JavaScript.
+                    currentWebView.getSettings().setJavaScriptEnabled(true);
+                } else if (currentWebView.getDomainSettingsApplied()) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
+                    // Apply the JavaScript preference that was stored the last time domain settings were loaded.
+                    currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
+                } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
+                    // Apply the JavaScript preference.
+                    currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
+                }
+
+                // Update the privacy icons.
+                updatePrivacyIcons(false);
+
+                // Reload the website.
+                currentWebView.reload();
 
-        // Get a handle for the cookie manager.
-        CookieManager cookieManager = CookieManager.getInstance();
+                // Consume the event.
+                return true;
 
-        // Initialize the current user agent string and the font size.
-        String currentUserAgent = getString(R.string.user_agent_privacy_browser);
-        int fontSize = 100;
+            case R.id.find_on_page:
+                // Get a handle for the views.
+                Toolbar toolbar = findViewById(R.id.toolbar);
+                LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+                EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
 
-        // 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);
-            }
+                // Set the minimum height of the find on page linear layout to match the toolbar.
+                findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
 
-            // Get the current user agent from the WebView.
-            currentUserAgent = currentWebView.getSettings().getUserAgentString();
+                // Hide the toolbar.
+                toolbar.setVisibility(View.GONE);
 
-            // Get the current font size from the
-            fontSize = currentWebView.getSettings().getTextZoom();
+                // Show the find on page linear layout.
+                findOnPageLinearLayout.setVisibility(View.VISIBLE);
 
-            // Set the status of the menu item checkboxes.
-            domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
-            saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
-            easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
-            easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
-            fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
-            fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
-            ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
-            blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
-            swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
-            wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
-            displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
-            nightModeMenuItem.setChecked(currentWebView.getNightMode());
+                // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
+                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+                findOnPageEditText.postDelayed(() -> {
+                    // Set the focus on `findOnPageEditText`.
+                    findOnPageEditText.requestFocus();
 
-            // Initialize the display names for the blocklists with the number of blocked requests.
-            blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-            easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
-            easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
-            fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
-            fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
-            ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
-            blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
+                    // Get a handle for the input method manager.
+                    InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-            // Only modify third-party cookies if the API >= 21.
-            if (Build.VERSION.SDK_INT >= 21) {
-                // Set the status of the third-party cookies checkbox.
-                thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
+                    // Remove the lint warning below that the input method manager might be null.
+                    assert inputMethodManager != null;
 
-                // Enable third-party cookies if first-party cookies are enabled.
-                thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
-            }
+                    // Display the keyboard.  `0` sets no input flags.
+                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
+                }, 200);
 
-            // Enable DOM Storage if JavaScript is enabled.
-            domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
-        }
+                // Consume the event.
+                return true;
 
-        // Set the status of the menu item checkboxes.
-        firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
-        proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
+            case R.id.print:
+                // Get a print manager instance.
+                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
 
-        // Enable Clear Cookies if there are any.
-        clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
+                // Remove the lint error below that print manager might be null.
+                assert printManager != null;
 
-        // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
-        String privateDataDirectoryString = getApplicationInfo().dataDir;
+                // Create a print document adapter from the current WebView.
+                PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
 
-        // Get a count of the number of files in the Local Storage directory.
-        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
-        int localStorageDirectoryNumberOfFiles = 0;
-        if (localStorageDirectory.exists()) {
-            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
-        }
+                // Print the document.
+                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
 
-        // Get a count of the number of files in the IndexedDB directory.
-        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
-        int indexedDBDirectoryNumberOfFiles = 0;
-        if (indexedDBDirectory.exists()) {
-            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
-        }
+                // Consume the event.
+                return true;
 
-        // Enable Clear DOM Storage if there is any.
-        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
+            case R.id.save_as_image:
+                // Instantiate the save webpage image dialog.
+                DialogFragment saveWebpageImageDialogFragment = new SaveWebpageImageDialog();
 
-        // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
-        if (Build.VERSION.SDK_INT < 26) {
-            // Get the WebView database.
-            WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
+                // Show the save webpage image dialog.
+                saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_as_image));
 
-            // Enable the clear form data menu item if there is anything to clear.
-            clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
-        }
+                // Consume the event.
+                return true;
 
-        // Enable Clear Data if any of the submenu items are enabled.
-        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+            case R.id.add_to_homescreen:
+                // Instantiate the create home screen shortcut dialog.
+                DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
+                        currentWebView.getFavoriteOrDefaultIcon());
 
-        // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
-        fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
+                // Show the create home screen shortcut dialog.
+                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
 
-        // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
-        if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
-            menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
-        } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
-            menu.findItem(R.id.user_agent_webview_default).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
-            menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
-            menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
-            menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
-            menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
-            menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
-            menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
-            menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
-            menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
-            menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
-            menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
-        } else {  // Custom user agent.
-            menu.findItem(R.id.user_agent_custom).setChecked(true);
-        }
+                // Consume the event.
+                return true;
 
-        // Instantiate the font size title and the selected font size menu item.
-        String fontSizeTitle;
-        MenuItem selectedFontSizeMenuItem;
+            case R.id.view_source:
+                // Create an intent to launch the view source activity.
+                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
 
-        // Prepare the font size title and current size menu item.
-        switch (fontSize) {
-            case 25:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
-                break;
+                // Add the variables to the intent.
+                viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
+                viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
 
-            case 50:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
-                break;
+                // Make it so.
+                startActivity(viewSourceIntent);
 
-            case 75:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
-                break;
+                // Consume the event.
+                return true;
 
-            case 100:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
-                break;
+            case R.id.share_url:
+                // Setup the share string.
+                String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
 
-            case 125:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
-                break;
+                // Create the share intent.
+                Intent shareIntent = new Intent(Intent.ACTION_SEND);
+                shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
+                shareIntent.setType("text/plain");
 
-            case 150:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
-                break;
+                // Make it so.
+                startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
 
-            case 175:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
-                break;
+                // Consume the event.
+                return true;
 
-            case 200:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
-                break;
+            case R.id.open_with_app:
+                // Open the URL with an outside app.
+                openWithApp(currentWebView.getUrl());
 
-            default:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
-                break;
-        }
+                // Consume the event.
+                return true;
 
-        // Set the font size title and select the current size menu item.
-        fontSizeMenuItem.setTitle(fontSizeTitle);
-        selectedFontSizeMenuItem.setChecked(true);
+            case R.id.open_with_browser:
+                // Open the URL with an outside browser.
+                openWithBrowser(currentWebView.getUrl());
 
-        // Run all the other default commands.
-        super.onPrepareOptionsMenu(menu);
+                // Consume the event.
+                return true;
 
-        // Display the menu.
-        return true;
-    }
+            case R.id.proxy_through_orbot:
+                // Toggle the proxy through Orbot variable.
+                proxyThroughOrbot = !proxyThroughOrbot;
 
-    @Override
-    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
-    @SuppressLint("SetJavaScriptEnabled")
-    public boolean onOptionsItemSelected(MenuItem menuItem) {
-        // Reenter full screen browsing mode if it was interrupted by the options menu.  <https://redmine.stoutner.com/issues/389>
-        if (inFullScreenBrowsingMode) {
-            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                // Apply the proxy through Orbot settings.
+                applyProxyThroughOrbot(true);
+
+                // Consume the event.
+                return true;
+
+            case R.id.refresh:
+                if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
+                    // Reload the current WebView.
+                    currentWebView.reload();
+                } else {  // The stop button was pushed.
+                    // Stop the loading of the WebView.
+                    currentWebView.stopLoading();
+                }
+
+                // Consume the event.
+                return true;
 
-            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+            case R.id.ad_consent:
+                // Instantiate the ad consent dialog.
+                DialogFragment adConsentDialogFragment = new AdConsentDialog();
 
-            /* Hide the system bars.
-             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                // Display the ad consent dialog.
+                adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
+
+                // Consume the event.
+                return true;
+
+            default:
+                // Don't consume the event.
+                return super.onOptionsItemSelected(menuItem);
         }
+    }
 
-        // Get the selected menu item ID.
+    // removeAllCookies is deprecated, but it is required for API < 21.
+    @Override
+    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
+        // Get the menu item ID.
         int menuItemId = menuItem.getItemId();
 
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get a handle for the cookie manager.
-        CookieManager cookieManager = CookieManager.getInstance();
-
-        // Run the commands that correlate to the selected menu item.
+        // Run the commands that correspond to the selected menu item.
         switch (menuItemId) {
-            case R.id.toggle_javascript:
-                // Toggle the JavaScript status.
-                currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
+            case R.id.clear_and_exit:
+                // Clear and exit Privacy Browser.
+                clearAndExit();
+                break;
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+            case R.id.home:
+                // Select the homepage based on the proxy through Orbot status.
+                if (proxyThroughOrbot) {
+                    // Load the Tor homepage.
+                    loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
+                } else {
+                    // Load the normal homepage.
+                    loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
+                }
+                break;
 
-                // Display a `Snackbar`.
-                if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
-                } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
+            case R.id.back:
+                if (currentWebView.canGoBack()) {
+                    // Get the current web back forward list.
+                    WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
+
+                    // Get the previous entry URL.
+                    String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
+
+                    // Reset the current domain name so that navigation works if third-party requests are blocked.
+                    currentWebView.resetCurrentDomainName();
+
+                    // Apply the domain settings.
+                    applyDomainSettings(currentWebView, previousUrl, false, false);
+
+                    // Load the previous website in the history.
+                    currentWebView.goBack();
                 }
+                break;
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+            case R.id.forward:
+                if (currentWebView.canGoForward()) {
+                    // Get the current web back forward list.
+                    WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
 
-            case R.id.add_or_edit_domain:
-                if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
-                    // Reapply the domain settings on returning to `MainWebViewActivity`.
-                    reapplyDomainSettingsOnRestart = true;
+                    // Get the next entry URL.
+                    String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
 
-                    // Create an intent to launch the domains activity.
-                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+                    // Reset the current domain name so that navigation works if third-party requests are blocked.
+                    currentWebView.resetCurrentDomainName();
 
-                    // Add the extra information to the intent.
-                    domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
-                    domainsIntent.putExtra("close_on_back", true);
-                    domainsIntent.putExtra("current_url", currentWebView.getUrl());
+                    // Apply the domain settings.
+                    applyDomainSettings(currentWebView, nextUrl, false, false);
 
-                    // Get the current certificate.
-                    SslCertificate sslCertificate = currentWebView.getCertificate();
+                    // Load the next website in the history.
+                    currentWebView.goForward();
+                }
+                break;
 
-                    // Check to see if the SSL certificate is populated.
-                    if (sslCertificate != null) {
-                        // Extract the certificate to strings.
-                        String issuedToCName = sslCertificate.getIssuedTo().getCName();
-                        String issuedToOName = sslCertificate.getIssuedTo().getOName();
-                        String issuedToUName = sslCertificate.getIssuedTo().getUName();
-                        String issuedByCName = sslCertificate.getIssuedBy().getCName();
-                        String issuedByOName = sslCertificate.getIssuedBy().getOName();
-                        String issuedByUName = sslCertificate.getIssuedBy().getUName();
-                        long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
-                        long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+            case R.id.history:
+                // Instantiate the URL history dialog.
+                DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
 
-                        // Add the certificate to the intent.
-                        domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
-                        domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
-                        domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
-                        domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
-                        domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
-                        domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
-                        domainsIntent.putExtra("ssl_start_date", startDateLong);
-                        domainsIntent.putExtra("ssl_end_date", endDateLong);
-                    }
+                // Show the URL history dialog.
+                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
+                break;
 
-                    // Check to see if the current IP addresses have been received.
-                    if (currentWebView.hasCurrentIpAddresses()) {
-                        // Add the current IP addresses to the intent.
-                        domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
-                    }
+            case R.id.requests:
+                // Populate the resource requests.
+                RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
 
-                    // Make it so.
-                    startActivity(domainsIntent);
-                } else {  // Add a new domain.
-                    // Apply the new domain settings on returning to `MainWebViewActivity`.
-                    reapplyDomainSettingsOnRestart = true;
+                // Create an intent to launch the Requests activity.
+                Intent requestsIntent = new Intent(this, RequestsActivity.class);
 
-                    // Get the current domain
-                    Uri currentUri = Uri.parse(currentWebView.getUrl());
-                    String currentDomain = currentUri.getHost();
+                // Add the block third-party requests status to the intent.
+                requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
 
-                    // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-                    DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+                // Make it so.
+                startActivity(requestsIntent);
+                break;
 
-                    // Create the domain and store the database ID.
-                    int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
+            case R.id.downloads:
+                // Launch the system Download Manager.
+                Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
 
-                    // Create an intent to launch the domains activity.
-                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+                // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
+                downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                    // Add the extra information to the intent.
-                    domainsIntent.putExtra("load_domain", newDomainDatabaseId);
-                    domainsIntent.putExtra("close_on_back", true);
-                    domainsIntent.putExtra("current_url", currentWebView.getUrl());
+                startActivity(downloadManagerIntent);
+                break;
 
-                    // Get the current certificate.
-                    SslCertificate sslCertificate = currentWebView.getCertificate();
+            case R.id.domains:
+                // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
+                reapplyDomainSettingsOnRestart = true;
 
-                    // Check to see if the SSL certificate is populated.
-                    if (sslCertificate != null) {
-                        // Extract the certificate to strings.
-                        String issuedToCName = sslCertificate.getIssuedTo().getCName();
-                        String issuedToOName = sslCertificate.getIssuedTo().getOName();
-                        String issuedToUName = sslCertificate.getIssuedTo().getUName();
-                        String issuedByCName = sslCertificate.getIssuedBy().getCName();
-                        String issuedByOName = sslCertificate.getIssuedBy().getOName();
-                        String issuedByUName = sslCertificate.getIssuedBy().getUName();
-                        long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
-                        long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+                // Launch the domains activity.
+                Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
-                        // Add the certificate to the intent.
-                        domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
-                        domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
-                        domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
-                        domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
-                        domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
-                        domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
-                        domainsIntent.putExtra("ssl_start_date", startDateLong);
-                        domainsIntent.putExtra("ssl_end_date", endDateLong);
-                    }
+                // Add the extra information to the intent.
+                domainsIntent.putExtra("current_url", currentWebView.getUrl());
 
-                    // Check to see if the current IP addresses have been received.
-                    if (currentWebView.hasCurrentIpAddresses()) {
-                        // Add the current IP addresses to the intent.
-                        domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
-                    }
+                // Get the current certificate.
+                SslCertificate sslCertificate = currentWebView.getCertificate();
 
-                    // Make it so.
-                    startActivity(domainsIntent);
+                // Check to see if the SSL certificate is populated.
+                if (sslCertificate != null) {
+                    // Extract the certificate to strings.
+                    String issuedToCName = sslCertificate.getIssuedTo().getCName();
+                    String issuedToOName = sslCertificate.getIssuedTo().getOName();
+                    String issuedToUName = sslCertificate.getIssuedTo().getUName();
+                    String issuedByCName = sslCertificate.getIssuedBy().getCName();
+                    String issuedByOName = sslCertificate.getIssuedBy().getOName();
+                    String issuedByUName = sslCertificate.getIssuedBy().getUName();
+                    long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+                    long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+
+                    // Add the certificate to the intent.
+                    domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+                    domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+                    domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+                    domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+                    domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+                    domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+                    domainsIntent.putExtra("ssl_start_date", startDateLong);
+                    domainsIntent.putExtra("ssl_end_date", endDateLong);
                 }
-                return true;
 
-            case R.id.toggle_first_party_cookies:
-                // Switch the first-party cookie status.
-                cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
+                // Check to see if the current IP addresses have been received.
+                if (currentWebView.hasCurrentIpAddresses()) {
+                    // Add the current IP addresses to the intent.
+                    domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                }
 
-                // Store the first-party cookie status.
-                currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
+                // Make it so.
+                startActivity(domainsIntent);
+                break;
 
-                // Update the menu checkbox.
-                menuItem.setChecked(cookieManager.acceptCookie());
+            case R.id.settings:
+                // Set the flag to reapply app settings on restart when returning from Settings.
+                reapplyAppSettingsOnRestart = true;
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+                // Set the flag to reapply the domain settings on restart when returning from Settings.
+                reapplyDomainSettingsOnRestart = true;
+
+                // Launch the settings activity.
+                Intent settingsIntent = new Intent(this, SettingsActivity.class);
+                startActivity(settingsIntent);
+                break;
+
+            case R.id.import_export:
+                // Launch the import/export activity.
+                Intent importExportIntent = new Intent (this, ImportExportActivity.class);
+                startActivity(importExportIntent);
+                break;
+
+            case R.id.logcat:
+                // Launch the logcat activity.
+                Intent logcatIntent = new Intent(this, LogcatActivity.class);
+                startActivity(logcatIntent);
+                break;
 
-                // Display a snackbar.
-                if (cookieManager.acceptCookie()) {  // First-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
-                } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
-                }
+            case R.id.guide:
+                // Launch `GuideActivity`.
+                Intent guideIntent = new Intent(this, GuideActivity.class);
+                startActivity(guideIntent);
+                break;
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+            case R.id.about:
+                // Create an intent to launch the about activity.
+                Intent aboutIntent = new Intent(this, AboutActivity.class);
 
-            case R.id.toggle_third_party_cookies:
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Switch the status of thirdPartyCookiesEnabled.
-                    cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
+                // Create a string array for the blocklist versions.
+                String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0],
+                        ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
 
-                    // Update the menu checkbox.
-                    menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
+                // Add the blocklist versions to the intent.
+                aboutIntent.putExtra("blocklist_versions", blocklistVersions);
 
-                    // Display a snackbar.
-                    if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
-                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                    } else {
-                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
-                    }
+                // Make it so.
+                startActivity(aboutIntent);
+                break;
+        }
 
-                    // Reload the current WebView.
-                    currentWebView.reload();
-                } // Else do nothing because SDK < 21.
-                return true;
+        // Get a handle for the drawer layout.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-            case R.id.toggle_dom_storage:
-                // Toggle the status of domStorageEnabled.
-                currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
+        // Close the navigation drawer.
+        drawerLayout.closeDrawer(GravityCompat.START);
+        return true;
+    }
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
+    @Override
+    public void onPostCreate(Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onPostCreate(savedInstanceState);
 
-                // Update the privacy icon.  `true` refreshes the app bar icons.
-                updatePrivacyIcons(true);
+        // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
+        actionBarDrawerToggle.syncState();
+    }
 
-                // Display a snackbar.
-                if (currentWebView.getSettings().getDomStorageEnabled()) {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
-                } else {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
-                }
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        // Run the default commands.
+        super.onConfigurationChanged(newConfig);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+        // Get the status bar pixel size.
+        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
 
-            // Form data can be removed once the minimum API >= 26.
-            case R.id.toggle_save_form_data:
-                // Switch the status of saveFormDataEnabled.
-                currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
+        // Get the resource density.
+        float screenDensity = getResources().getDisplayMetrics().density;
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
+        // Recalculate the drawer header padding.
+        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
 
-                // Display a snackbar.
-                if (currentWebView.getSettings().getSaveFormData()) {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
-                } else {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
-                }
+        // Reload the ad for the free flavor if not in full screen mode.
+        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+        }
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+        // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
+        // https://code.google.com/p/android/issues/detail?id=20493#c8
+        // ActivityCompat.invalidateOptionsMenu(this);
+    }
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
+        // Store the hit test result.
+        final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
 
-            case R.id.clear_cookies:
-                Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
-                                    // Delete the cookies, which command varies by SDK.
-                                    if (Build.VERSION.SDK_INT < 21) {
-                                        cookieManager.removeAllCookie();
-                                    } else {
-                                        cookieManager.removeAllCookies(null);
-                                    }
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+        // Define the URL strings.
+        final String imageUrl;
+        final String linkUrl;
 
-            case R.id.clear_dom_storage:
-                Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
-                                    // Delete the DOM Storage.
-                                    WebStorage webStorage = WebStorage.getInstance();
-                                    webStorage.deleteAllData();
+        // Get handles for the system managers.
+        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+        FragmentManager fragmentManager = getSupportFragmentManager();
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                                    // Initialize a handler to manually delete the DOM storage files and directories.
-                                    Handler deleteDomStorageHandler = new Handler();
+        // Remove the lint errors below that the clipboard manager might be null.
+        assert clipboardManager != null;
 
-                                    // Setup a runnable to manually delete the DOM storage files and directories.
-                                    Runnable deleteDomStorageRunnable = () -> {
-                                        try {
-                                            // Get a handle for the runtime.
-                                            Runtime runtime = Runtime.getRuntime();
+        // Process the link according to the type.
+        switch (hitTestResult.getType()) {
+            // `SRC_ANCHOR_TYPE` is a link.
+            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
+                // Get the target URL.
+                linkUrl = hitTestResult.getExtra();
 
-                                            // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
-                                            // which links to `/data/data/com.stoutner.privacybrowser.standard`.
-                                            String privateDataDirectoryString = getApplicationInfo().dataDir;
+                // Set the target URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(linkUrl);
 
-                                            // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
-                                            Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+                // Add an Open in New Tab entry.
+                menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Load the link URL in a new tab.
+                    addNewTab(linkUrl, false);
 
-                                            // Multiple commands must be used because `Runtime.exec()` does not like `*`.
-                                            Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
-                                            Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
-                                            Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
-                                            Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+                    // Consume the event.
+                    return true;
+                });
 
-                                            // Wait for the processes to finish.
-                                            deleteLocalStorageProcess.waitFor();
-                                            deleteIndexProcess.waitFor();
-                                            deleteQuotaManagerProcess.waitFor();
-                                            deleteQuotaManagerJournalProcess.waitFor();
-                                            deleteDatabasesProcess.waitFor();
-                                        } catch (Exception exception) {
-                                            // Do nothing if an error is thrown.
-                                        }
-                                    };
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithApp(linkUrl);
 
-                                    // Manually delete the DOM storage files after 200 milliseconds.
-                                    deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            // Form data can be remove once the minimum API >= 26.
-            case R.id.clear_form_data:
-                Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
-                                    // Delete the form data.
-                                    WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
-                                    mainWebViewDatabase.clearFormData();
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithBrowser(linkUrl);
 
-            case R.id.easylist:
-                // Toggle the EasyList status.
-                currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
+                    // Consume the event.
+                    return true;
+                });
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Save the link URL in a `ClipData`.
+                    ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
 
-            case R.id.easyprivacy:
-                // Toggle the EasyPrivacy status.
-                currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
+                    // Consume the event.
+                    return true;
+                });
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
+                // Add a Download URL entry.
+                menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
+                        openUrlWithExternalApp(linkUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the variables for future use by `onRequestPermissionsResult()`.
+                            downloadUrl = linkUrl;
+                            downloadContentDisposition = "none";
+                            downloadContentLength = -1;
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
 
-            case R.id.fanboys_annoyance_list:
-                // Toggle Fanboy's Annoyance List status.
-                currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+                                // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download file alert dialog.
+                            DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+                            // Show the download file alert dialog.
+                            downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
 
-                // Update the staus of Fanboy's Social Blocking List.
-                MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
-                fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+                    // Consume the event.
+                    return true;
+                });
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                // Add a Cancel entry, which by default closes the context menu.
+                menu.add(R.string.cancel);
+                break;
 
-            case R.id.fanboys_social_blocking_list:
-                // Toggle Fanboy's Social Blocking List status.
-                currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+            case WebView.HitTestResult.EMAIL_TYPE:
+                // Get the target URL.
+                linkUrl = hitTestResult.getExtra();
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+                // Set the target URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(linkUrl);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                // Add a Write Email entry.
+                menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
+                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
-            case R.id.ultraprivacy:
-                // Toggle the UltraPrivacy status.
-                currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
+                    // Parse the url and set it as the data for the `Intent`.
+                    emailIntent.setData(Uri.parse("mailto:" + linkUrl));
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
+                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
+                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Make it so.
+                    startActivity(emailIntent);
 
-            case R.id.block_all_third_party_requests:
-                //Toggle the third-party requests blocker status.
-                currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+                    // Consume the event.
+                    return true;
+                });
 
-                // Update the menu checkbox.
-                menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+                // Add a Copy Email Address entry.
+                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
+                    // Save the email address in a `ClipData`.
+                    ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcEmailTypeClipData);
 
-            case R.id.user_agent_privacy_browser:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
+                    // Consume the event.
+                    return true;
+                });
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+                menu.add(R.string.cancel);
+                break;
 
-            case R.id.user_agent_webview_default:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString("");
+            // `IMAGE_TYPE` is an image.
+            case WebView.HitTestResult.IMAGE_TYPE:
+                // Get the image URL.
+                imageUrl = hitTestResult.getExtra();
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                // Set the image URL as the title of the context menu.
+                menu.setHeaderTitle(imageUrl);
 
-            case R.id.user_agent_firefox_on_android:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
+                // Add an Open in New Tab entry.
+                menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Load the image in a new tab.
+                    addNewTab(imageUrl, false);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.user_agent_chrome_on_android:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
+                // Add a View Image entry.
+                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+                    // Load the image in the current tab.
+                    loadUrl(imageUrl);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.user_agent_safari_on_ios:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
+                // Add a Download Image entry.
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
 
-            case R.id.user_agent_firefox_on_linux:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
 
-            case R.id.user_agent_chromium_on_linux:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
+                    // Consume the event.
+                    return true;
+                });
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Save the image URL in a clip data.
+                    ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
 
-            case R.id.user_agent_firefox_on_windows:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
+                    // Set the clip data as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(imageTypeClipData);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.user_agent_chrome_on_windows:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Open the image URL with an external app.
+                    openWithApp(imageUrl);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.user_agent_edge_on_windows:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Open the image URL with an external browser.
+                    openWithBrowser(imageUrl);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.user_agent_internet_explorer_on_windows:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
+                // Add a Cancel entry, which by default closes the context menu.
+                menu.add(R.string.cancel);
+                break;
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+            // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
+            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
+                // Get the image URL.
+                imageUrl = hitTestResult.getExtra();
 
-            case R.id.user_agent_safari_on_macos:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
+                // Instantiate a handler.
+                Handler handler = new Handler();
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                // Get a message from the handler.
+                Message message = handler.obtainMessage();
 
-            case R.id.user_agent_custom:
-                // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+                // Request the image details from the last touched node be returned in the message.
+                currentWebView.requestFocusNodeHref(message);
 
-                // Reload the current WebView.
-                currentWebView.reload();
-                return true;
+                // Get the link URL from the message data.
+                linkUrl = message.getData().getString("url");
 
-            case R.id.font_size_twenty_five_percent:
-                currentWebView.getSettings().setTextZoom(25);
-                return true;
+                // Set the link URL as the title of the context menu.
+                menu.setHeaderTitle(linkUrl);
 
-            case R.id.font_size_fifty_percent:
-                currentWebView.getSettings().setTextZoom(50);
-                return true;
+                // Add an Open in New Tab entry.
+                menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Load the link URL in a new tab.
+                    addNewTab(linkUrl, false);
 
-            case R.id.font_size_seventy_five_percent:
-                currentWebView.getSettings().setTextZoom(75);
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.font_size_one_hundred_percent:
-                currentWebView.getSettings().setTextZoom(100);
-                return true;
+                // Add an Open Image in New Tab entry.
+                menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Load the image in a new tab.
+                    addNewTab(imageUrl, false);
 
-            case R.id.font_size_one_hundred_twenty_five_percent:
-                currentWebView.getSettings().setTextZoom(125);
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.font_size_one_hundred_fifty_percent:
-                currentWebView.getSettings().setTextZoom(150);
-                return true;
+                // Add a View Image entry.
+                menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
+                   // View the image in the current tab.
+                   loadUrl(imageUrl);
 
-            case R.id.font_size_one_hundred_seventy_five_percent:
-                currentWebView.getSettings().setTextZoom(175);
-                return true;
+                   // Consume the event.
+                   return true;
+                });
 
-            case R.id.font_size_two_hundred_percent:
-                currentWebView.getSettings().setTextZoom(200);
-                return true;
+                // Add a Download Image entry.
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
 
-            case R.id.swipe_to_refresh:
-                // Toggle the stored status of swipe to refresh.
-                currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
 
-                // Get a handle for the swipe refresh layout.
-                SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-                // Update the swipe refresh layout.
-                if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
-                    if (Build.VERSION.SDK_INT >= 23) {  // For API >= 23, the status of the scroll refresh listener is continuously updated by the on scroll change listener.
-                        // Only enable the swipe refresh layout if the WebView is scrolled to the top.
-                        swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
-                    } else {  // For API < 23, the swipe refresh layout is always enabled.
-                        // Enable the swipe refresh layout.
-                        swipeRefreshLayout.setEnabled(true);
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
                     }
-                } else {  // Swipe to refresh is disabled.
-                    // Disable the swipe refresh layout.
-                    swipeRefreshLayout.setEnabled(false);
-                }
-                return true;
 
-            case R.id.wide_viewport:
-                // Toggle the viewport.
-                currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
-                return true;
+                    // Consume the event.
+                    return true;
+                });
 
-            case R.id.display_images:
-                if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
-                    // Disable loading of images.
-                    currentWebView.getSettings().setLoadsImagesAutomatically(false);
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Save the link URL in a clip data.
+                    ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
 
-                    // Reload the website to remove existing images.
-                    currentWebView.reload();
-                } else {  // Images are not currently loaded automatically.
-                    // Enable loading of images.  Missing images will be loaded without the need for a reload.
-                    currentWebView.getSettings().setLoadsImagesAutomatically(true);
-                }
-                return true;
+                    // Set the clip data as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
 
-            case R.id.night_mode:
-                // Toggle night mode.
-                currentWebView.setNightMode(!currentWebView.getNightMode());
+                    // Consume the event.
+                    return true;
+                });
 
-                // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
-                if (currentWebView.getNightMode()) {  // Night mode is enabled, which requires JavaScript.
-                    // Enable JavaScript.
-                    currentWebView.getSettings().setJavaScriptEnabled(true);
-                } else if (currentWebView.getDomainSettingsApplied()) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
-                    // Apply the JavaScript preference that was stored the last time domain settings were loaded.
-                    currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
-                } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
-                    // Apply the JavaScript preference.
-                    currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
-                }
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Open the link URL with an external app.
+                    openWithApp(linkUrl);
 
-                // Update the privacy icons.
-                updatePrivacyIcons(false);
+                    // Consume the event.
+                    return true;
+                });
 
-                // Reload the website.
-                currentWebView.reload();
-                return true;
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Open the link URL with an external browser.
+                    openWithBrowser(linkUrl);
 
-            case R.id.find_on_page:
-                // Get a handle for the views.
-                Toolbar toolbar = findViewById(R.id.toolbar);
-                LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
-                EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+                    // Consume the event.
+                    return true;
+                });
 
-                // Set the minimum height of the find on page linear layout to match the toolbar.
-                findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
+                // Add a cancel entry, which by default closes the context menu.
+                menu.add(R.string.cancel);
+                break;
+        }
+    }
 
-                // Hide the toolbar.
-                toolbar.setVisibility(View.GONE);
+    @Override
+    public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
+        // Get a handle for the bookmarks list view.
+        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
 
-                // Show the find on page linear layout.
-                findOnPageLinearLayout.setVisibility(View.VISIBLE);
+        // Get the views from the dialog fragment.
+        EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
+        EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
 
-                // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
-                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
-                findOnPageEditText.postDelayed(() -> {
-                    // Set the focus on `findOnPageEditText`.
-                    findOnPageEditText.requestFocus();
+        // Extract the strings from the edit texts.
+        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
 
-                    // Get a handle for the input method manager.
-                    InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+        // Create a favorite icon byte array output stream.
+        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                    // Remove the lint warning below that the input method manager might be null.
-                    assert inputMethodManager != null;
+        // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
 
-                    // Display the keyboard.  `0` sets no input flags.
-                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
-                }, 200);
-                return true;
+        // Convert the favorite icon byte array stream to a byte array.
+        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
 
-            case R.id.view_source:
-                // Create an intent to launch the view source activity.
-                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+        // Display the new bookmark below the current items in the (0 indexed) list.
+        int newBookmarkDisplayOrder = bookmarksListView.getCount();
 
-                // Add the variables to the intent.
-                viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
-                viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
+        // Create the bookmark.
+        bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
 
-                // Make it so.
-                startActivity(viewSourceIntent);
-                return true;
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-            case R.id.share_url:
-                // Setup the share string.
-                String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
+        // Update the list view.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
 
-                // Create the share intent.
-                Intent shareIntent = new Intent(Intent.ACTION_SEND);
-                shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
-                shareIntent.setType("text/plain");
+        // Scroll to the new bookmark.
+        bookmarksListView.setSelection(newBookmarkDisplayOrder);
+    }
 
-                // Make it so.
-                startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
-                return true;
+    @Override
+    public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
+        // Get a handle for the bookmarks list view.
+        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
 
-            case R.id.print:
-                // Get a print manager instance.
-                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+        // Get handles for the views in the dialog fragment.
+        EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
+        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
 
-                // Remove the lint error below that print manager might be null.
-                assert printManager != null;
+        // Get new folder name string.
+        String folderNameString = createFolderNameEditText.getText().toString();
 
-                // Create a print document adapter from the current WebView.
-                PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
+        // Create a folder icon bitmap.
+        Bitmap folderIconBitmap;
 
-                // Print the document.
-                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
-                return true;
+        // Set the folder icon bitmap according to the dialog.
+        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
+            // Get the default folder icon drawable.
+            Drawable folderIconDrawable = folderIconImageView.getDrawable();
 
-            case R.id.open_with_app:
-                openWithApp(currentWebView.getUrl());
-                return true;
+            // Convert the folder icon drawable to a bitmap drawable.
+            BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-            case R.id.open_with_browser:
-                openWithBrowser(currentWebView.getUrl());
-                return true;
+            // Convert the folder icon bitmap drawable to a bitmap.
+            folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+        } else {  // Use the WebView favorite icon.
+            // Copy the favorite icon bitmap to the folder icon bitmap.
+            folderIconBitmap = favoriteIconBitmap;
+        }
 
-            case R.id.add_to_homescreen:
-                // Instantiate the create home screen shortcut dialog.
-                DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
-                        currentWebView.getFavoriteOrDefaultIcon());
+        // Create a folder icon byte array output stream.
+        ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Show the create home screen shortcut dialog.
-                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
-                return true;
+        // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
 
-            case R.id.proxy_through_orbot:
-                // Toggle the proxy through Orbot variable.
-                proxyThroughOrbot = !proxyThroughOrbot;
+        // Convert the folder icon byte array stream to a byte array.
+        byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
 
-                // Apply the proxy through Orbot settings.
-                applyProxyThroughOrbot(true);
-                return true;
+        // Move all the bookmarks down one in the display order.
+        for (int i = 0; i < bookmarksListView.getCount(); i++) {
+            int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
+            bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
+        }
 
-            case R.id.refresh:
-                if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
-                    // Reload the current WebView.
-                    currentWebView.reload();
-                } else {  // The stop button was pushed.
-                    // Stop the loading of the WebView.
-                    currentWebView.stopLoading();
-                }
-                return true;
+        // Create the folder, which will be placed at the top of the `ListView`.
+        bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
 
-            case R.id.ad_consent:
-                // Display the ad consent dialog.
-                DialogFragment adConsentDialogFragment = new AdConsentDialog();
-                adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
-                return true;
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-            default:
-                // Don't consume the event.
-                return super.onOptionsItemSelected(menuItem);
-        }
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+
+        // Scroll to the new folder.
+        bookmarksListView.setSelection(0);
     }
 
-    // removeAllCookies is deprecated, but it is required for API < 21.
     @Override
-    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
-        // Get the menu item ID.
-        int menuItemId = menuItem.getItemId();
+    public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
+        EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
+        RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
 
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        // Store the bookmark strings.
+        String bookmarkNameString = editBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
 
-        // Run the commands that correspond to the selected menu item.
-        switch (menuItemId) {
-            case R.id.clear_and_exit:
-                // Clear and exit Privacy Browser.
-                clearAndExit();
-                break;
+        // Update the bookmark.
+        if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
+        } else {  // Update the bookmark using the `WebView` favorite icon.
+            // Create a favorite icon byte array output stream.
+            ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-            case R.id.home:
-                // Select the homepage based on the proxy through Orbot status.
-                if (proxyThroughOrbot) {
-                    // Load the Tor homepage.
-                    loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
-                } else {
-                    // Load the normal homepage.
-                    loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
-                }
-                break;
+            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
 
-            case R.id.back:
-                if (currentWebView.canGoBack()) {
-                    // Reset the current domain name so that navigation works if third-party requests are blocked.
-                    currentWebView.resetCurrentDomainName();
+            // Convert the favorite icon byte array stream to a byte array.
+            byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
 
-                    // Set navigating history so that the domain settings are applied when the new URL is loaded.
-                    currentWebView.setNavigatingHistory(true);
+            //  Update the bookmark and the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
+        }
 
-                    // Load the previous website in the history.
-                    currentWebView.goBack();
-                }
-                break;
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-            case R.id.forward:
-                if (currentWebView.canGoForward()) {
-                    // Reset the current domain name so that navigation works if third-party requests are blocked.
-                    currentWebView.resetCurrentDomainName();
+        // Update the list view.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
 
-                    // Set navigating history so that the domain settings are applied when the new URL is loaded.
-                    currentWebView.setNavigatingHistory(true);
+    @Override
+    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
+        RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
+        ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
 
-                    // Load the next website in the history.
-                    currentWebView.goForward();
-                }
-                break;
+        // Get the new folder name.
+        String newFolderNameString = editFolderNameEditText.getText().toString();
 
-            case R.id.history:
-                // Instantiate the URL history dialog.
-                DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
+        // Check if the favorite icon has changed.
+        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
+            // Update the name in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
+        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
+            // Create the new folder icon Bitmap.
+            Bitmap folderIconBitmap;
 
-                // Show the URL history dialog.
-                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
-                break;
+            // Populate the new folder icon bitmap.
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon drawable.
+                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
 
-            case R.id.requests:
-                // Populate the resource requests.
-                RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
+                // Convert the folder icon drawable to a bitmap drawable.
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-                // Create an intent to launch the Requests activity.
-                Intent requestsIntent = new Intent(this, RequestsActivity.class);
+                // Convert the folder icon bitmap drawable to a bitmap.
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                // Copy the favorite icon bitmap to the folder icon bitmap.
+                folderIconBitmap = favoriteIconBitmap;
+            }
 
-                // Add the block third-party requests status to the intent.
-                requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+            // Create a folder icon byte array output stream.
+            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Make it so.
-                startActivity(requestsIntent);
-                break;
+            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
 
-            case R.id.downloads:
-                // Launch the system Download Manager.
-                Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+            // Convert the folder icon byte array stream to a byte array.
+            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
 
-                // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
-                downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            // Update the folder icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
+        } else {  // The folder icon and the name have changed.
+            // Get the new folder icon `Bitmap`.
+            Bitmap folderIconBitmap;
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon drawable.
+                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
 
-                startActivity(downloadManagerIntent);
-                break;
+                // Convert the folder icon drawable to a bitmap drawable.
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-            case R.id.domains:
-                // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
-                reapplyDomainSettingsOnRestart = true;
+                // Convert the folder icon bitmap drawable to a bitmap.
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                // Copy the favorite icon bitmap to the folder icon bitmap.
+                folderIconBitmap = favoriteIconBitmap;
+            }
 
-                // Launch the domains activity.
-                Intent domainsIntent = new Intent(this, DomainsActivity.class);
+            // Create a folder icon byte array output stream.
+            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Add the extra information to the intent.
-                domainsIntent.putExtra("current_url", currentWebView.getUrl());
+            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
 
-                // Get the current certificate.
-                SslCertificate sslCertificate = currentWebView.getCertificate();
+            // Convert the folder icon byte array stream to a byte array.
+            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
 
-                // Check to see if the SSL certificate is populated.
-                if (sslCertificate != null) {
-                    // Extract the certificate to strings.
-                    String issuedToCName = sslCertificate.getIssuedTo().getCName();
-                    String issuedToOName = sslCertificate.getIssuedTo().getOName();
-                    String issuedToUName = sslCertificate.getIssuedTo().getUName();
-                    String issuedByCName = sslCertificate.getIssuedBy().getCName();
-                    String issuedByOName = sslCertificate.getIssuedBy().getOName();
-                    String issuedByUName = sslCertificate.getIssuedBy().getUName();
-                    long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
-                    long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+            // Update the folder name and icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
+        }
 
-                    // Add the certificate to the intent.
-                    domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
-                    domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
-                    domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
-                    domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
-                    domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
-                    domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
-                    domainsIntent.putExtra("ssl_start_date", startDateLong);
-                    domainsIntent.putExtra("ssl_end_date", endDateLong);
-                }
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-                // Check to see if the current IP addresses have been received.
-                if (currentWebView.hasCurrentIpAddresses()) {
-                    // Add the current IP addresses to the intent.
-                    domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
-                }
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
 
-                // Make it so.
-                startActivity(domainsIntent);
+    @Override
+    public void onCloseDownloadLocationPermissionDialog(int downloadType) {
+        switch (downloadType) {
+            case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
+                // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
                 break;
 
-            case R.id.settings:
-                // Set the flag to reapply app settings on restart when returning from Settings.
-                reapplyAppSettingsOnRestart = true;
+            case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
+                // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                break;
+        }
+    }
 
-                // Set the flag to reapply the domain settings on restart when returning from Settings.
-                reapplyDomainSettingsOnRestart = true;
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        // Get a handle for the fragment manager.
+        FragmentManager fragmentManager = getSupportFragmentManager();
 
-                // Launch the settings activity.
-                Intent settingsIntent = new Intent(this, SettingsActivity.class);
-                startActivity(settingsIntent);
-                break;
+        switch (requestCode) {
+            case DOWNLOAD_FILE_REQUEST_CODE:
+                // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
+                DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
 
-            case R.id.import_export:
-                // Launch the import/export activity.
-                Intent importExportIntent = new Intent (this, ImportExportActivity.class);
-                startActivity(importExportIntent);
-                break;
+                // On API 23, displaying the fragment must be delayed or the app will crash.
+                if (Build.VERSION.SDK_INT == 23) {
+                    new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
+                } else {
+                    downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
+                }
 
-            case R.id.logcat:
-                // Launch the logcat activity.
-                Intent logcatIntent = new Intent(this, LogcatActivity.class);
-                startActivity(logcatIntent);
+                // Reset the download variables.
+                downloadUrl = "";
+                downloadContentDisposition = "";
+                downloadContentLength = 0;
                 break;
 
-            case R.id.guide:
-                // Launch `GuideActivity`.
-                Intent guideIntent = new Intent(this, GuideActivity.class);
-                startActivity(guideIntent);
+            case DOWNLOAD_IMAGE_REQUEST_CODE:
+                // Show the download image alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
+                DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
+
+                // On API 23, displaying the fragment must be delayed or the app will crash.
+                if (Build.VERSION.SDK_INT == 23) {
+                    new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
+                } else {
+                    downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                }
+
+                // Reset the image URL variable.
+                downloadImageUrl = "";
                 break;
 
-            case R.id.about:
-                // Create an intent to launch the about activity.
-                Intent aboutIntent = new Intent(this, AboutActivity.class);
-
-                // Create a string array for the blocklist versions.
-                String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0],
-                        ultraPrivacy.get(0).get(0)[0]};
-
-                // Add the blocklist versions to the intent.
-                aboutIntent.putExtra("blocklist_versions", blocklistVersions);
+            case SAVE_WEBPAGE_IMAGE_REQUEST_CODE:
+                // Check to see if the storage permission was granted.  If the dialog was canceled the grant result will be empty.
+                if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {  // The storage permission was granted.
+                    // Save the webpage image.
+                    new SaveWebpageImage(this, currentWebView).execute(saveWebsiteImageFilePath);
+                } else {  // The storage permission was not granted.
+                    // Display an error snackbar.
+                    Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
+                }
 
-                // Make it so.
-                startActivity(aboutIntent);
+                // Reset the save website image file path.
+                saveWebsiteImageFilePath = "";
                 break;
         }
-
-        // Get a handle for the drawer layout.
-        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-
-        // Close the navigation drawer.
-        drawerLayout.closeDrawer(GravityCompat.START);
-        return true;
     }
 
     @Override
-    public void onPostCreate(Bundle savedInstanceState) {
-        // Run the default commands.
-        super.onPostCreate(savedInstanceState);
+    public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
+        // Download the image if it has an HTTP or HTTPS URI.
+        if (imageUrl.startsWith("http")) {
+            // Get a handle for the system `DOWNLOAD_SERVICE`.
+            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
 
-        // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
-        actionBarDrawerToggle.syncState();
-    }
+            // Parse `imageUrl`.
+            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        // Run the default commands.
-        super.onConfigurationChanged(newConfig);
+            // Get a handle for the cookie manager.
+            CookieManager cookieManager = CookieManager.getInstance();
 
-        // Get the status bar pixel size.
-        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
-        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
+            // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
+            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+            if (cookieManager.acceptCookie()) {
+                // Get the cookies for `imageUrl`.
+                String cookies = cookieManager.getCookie(imageUrl);
 
-        // Get the resource density.
-        float screenDensity = getResources().getDisplayMetrics().density;
+                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
+                downloadRequest.addRequestHeader("Cookie", cookies);
+            }
 
-        // Recalculate the drawer header padding.
-        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
-        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
-        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+            // Get the file name from the dialog fragment.
+            EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
+            String imageName = downloadImageNameEditText.getText().toString();
 
-        // Reload the ad for the free flavor if not in full screen mode.
-        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
-        }
+            // Specify the download location.
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
+                // Download to the public download directory.
+                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
+            } else {  // External write permission denied.
+                // Download to the app's external download directory.
+                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
+            }
 
-        // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
-        // https://code.google.com/p/android/issues/detail?id=20493#c8
-        // ActivityCompat.invalidateOptionsMenu(this);
-    }
+            // Allow `MediaScanner` to index the download if it is a media file.
+            downloadRequest.allowScanningByMediaScanner();
 
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
-        // Store the hit test result.
-        final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
+            // Add the URL as the description for the download.
+            downloadRequest.setDescription(imageUrl);
 
-        // Create the URL strings.
-        final String imageUrl;
-        final String linkUrl;
+            // Show the download notification after the download is completed.
+            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
-        // Get handles for the system managers.
-        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
-        FragmentManager fragmentManager = getSupportFragmentManager();
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
 
-        // Remove the lint errors below that the clipboard manager might be null.
-        assert clipboardManager != null;
+            // Initiate the download.
+            downloadManager.enqueue(downloadRequest);
+        } else {  // The image is not an HTTP or HTTPS URI.
+            Snackbar.make(currentWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
+        }
+    }
 
-        // Process the link according to the type.
-        switch (hitTestResult.getType()) {
-            // `SRC_ANCHOR_TYPE` is a link.
-            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
-                // Get the target URL.
-                linkUrl = hitTestResult.getExtra();
+    @Override
+    public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
+        // Download the file if it has an HTTP or HTTPS URI.
+        if (downloadUrl.startsWith("http")) {
+            // Get a handle for the system `DOWNLOAD_SERVICE`.
+            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
 
-                // Set the target URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(linkUrl);
+            // Parse `downloadUrl`.
+            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
 
-                // Add an Open in New Tab entry.
-                menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Load the link URL in a new tab.
-                    addNewTab(linkUrl);
-                    return false;
-                });
+            // Get a handle for the cookie manager.
+            CookieManager cookieManager = CookieManager.getInstance();
 
-                // Add an Open with App entry.
-                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithApp(linkUrl);
-                    return false;
-                });
+            // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
+            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+            if (cookieManager.acceptCookie()) {
+                // Get the cookies for `downloadUrl`.
+                String cookies = cookieManager.getCookie(downloadUrl);
 
-                // Add an Open with Browser entry.
-                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithBrowser(linkUrl);
-                    return false;
-                });
+                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
+                downloadRequest.addRequestHeader("Cookie", cookies);
+            }
 
-                // Add a Copy URL entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Save the link URL in a `ClipData`.
-                    ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
+            // Get the file name from the dialog fragment.
+            EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
+            String fileName = downloadFileNameEditText.getText().toString();
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
-                    return false;
-                });
+            // Specify the download location.
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
+                // Download to the public download directory.
+                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
+            } else {  // External write permission denied.
+                // Download to the app's external download directory.
+                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
+            }
 
-                // Add a Download URL entry.
-                menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
-                        openUrlWithExternalApp(linkUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the variables for future use by `onRequestPermissionsResult()`.
-                            downloadUrl = linkUrl;
-                            downloadContentDisposition = "none";
-                            downloadContentLength = -1;
+            // Allow `MediaScanner` to index the download if it is a media file.
+            downloadRequest.allowScanningByMediaScanner();
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+            // Add the URL as the description for the download.
+            downloadRequest.setDescription(downloadUrl);
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download file alert dialog.
-                            DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
+            // Show the download notification after the download is completed.
+            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
-                            // Show the download file alert dialog.
-                            downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
 
-                // Add a Cancel entry, which by default closes the context menu.
-                menu.add(R.string.cancel);
-                break;
+            // Initiate the download.
+            downloadManager.enqueue(downloadRequest);
+        } else {  // The download is not an HTTP or HTTPS URI.
+            Snackbar.make(currentWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
+        }
+    }
 
-            case WebView.HitTestResult.EMAIL_TYPE:
-                // Get the target URL.
-                linkUrl = hitTestResult.getExtra();
+    // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
+    @Override
+    public void onBackPressed() {
+        // Get a handle for the drawer layout and the tab layout.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        TabLayout tabLayout = findViewById(R.id.tablayout);
 
-                // Set the target URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(linkUrl);
+        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
+            // Close the navigation drawer.
+            drawerLayout.closeDrawer(GravityCompat.START);
+        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
+            if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
+                // close the bookmarks drawer.
+                drawerLayout.closeDrawer(GravityCompat.END);
+            } else {  // A subfolder is displayed.
+                // Place the former parent folder in `currentFolder`.
+                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
 
-                // Add a Write Email entry.
-                menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
-                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
-                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+                // Load the new folder.
+                loadBookmarksFolder();
+            }
+        } else if (displayingFullScreenVideo) {  // A full screen video is shown.
+            // Get a handle for the layouts.
+            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+            RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
+            FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
 
-                    // Parse the url and set it as the data for the `Intent`.
-                    emailIntent.setData(Uri.parse("mailto:" + linkUrl));
+            // Re-enable the screen timeout.
+            fullScreenVideoFrameLayout.setKeepScreenOn(false);
 
-                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
-                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            // Unset the full screen video flag.
+            displayingFullScreenVideo = false;
 
-                    // Make it so.
-                    startActivity(emailIntent);
-                    return false;
-                });
+            // Remove all the views from the full screen video frame layout.
+            fullScreenVideoFrameLayout.removeAllViews();
 
-                // Add a Copy Email Address entry.
-                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
-                    // Save the email address in a `ClipData`.
-                    ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
+            // Hide the full screen video frame layout.
+            fullScreenVideoFrameLayout.setVisibility(View.GONE);
+
+            // Enable the sliding drawers.
+            drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcEmailTypeClipData);
-                    return false;
-                });
+            // Show the main content relative layout.
+            mainContentRelativeLayout.setVisibility(View.VISIBLE);
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+            // Apply the appropriate full screen mode flags.
+            if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+                // Hide the app bar if specified.
+                if (hideAppBar) {
+                    // Get handles for the views.
+                    LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
+                    ActionBar actionBar = getSupportActionBar();
 
-            // `IMAGE_TYPE` is an image. `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.  Privacy Browser processes them the same.
-            case WebView.HitTestResult.IMAGE_TYPE:
-            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
-                // Get the image URL.
-                imageUrl = hitTestResult.getExtra();
+                    // Remove the incorrect lint warning below that the action bar might be null.
+                    assert actionBar != null;
 
-                // Set the image URL as the title of the context menu.
-                menu.setHeaderTitle(imageUrl);
+                    // Hide the tab linear layout.
+                    tabsLinearLayout.setVisibility(View.GONE);
 
-                // Add an Open in New Tab entry.
-                menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Load the image URL in a new tab.
-                    addNewTab(imageUrl);
-                    return false;
-                });
+                    // Hide the action bar.
+                    actionBar.hide();
+                }
 
-                // Add a View Image entry.
-                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
-                    loadUrl(imageUrl);
-                    return false;
-                });
+                // Hide the banner ad in the free flavor.
+                if (BuildConfig.FLAVOR.contentEquals("free")) {
+                    AdHelper.hideAd(findViewById(R.id.adview));
+                }
 
-                // Add a `Download Image` entry.
-                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
-                        openUrlWithExternalApp(imageUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the image URL for use by `onRequestPermissionResult()`.
-                            downloadImageUrl = imageUrl;
+                // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+                /* Hide the system bars.
+                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                 */
+                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+            } else {  // Switch to normal viewing mode.
+                // Remove the `SYSTEM_UI` flags from the root frame layout.
+                rootFrameLayout.setSystemUiVisibility(0);
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download image alert dialog.
-                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                // Add the translucent status flag.
+                getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            }
 
-                            // Show the download image alert dialog.
-                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+            // Reload the ad for the free flavor if not in full screen mode.
+            if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+                // Reload the ad.
+                AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+            }
+        } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
+            // Get the current web back forward list.
+            WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
 
-                // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
-                    // Save the image URL in a `ClipData`.
-                    ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+            // Get the previous entry URL.
+            String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
-                    return false;
-                });
+            // Reset the current domain name so that navigation works if third-party requests are blocked.
+            currentWebView.resetCurrentDomainName();
 
-                // Add an Open with App entry.
-                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithApp(imageUrl);
-                    return false;
-                });
+            // Apply the domain settings.
+            applyDomainSettings(currentWebView, previousUrl, false, false);
 
-                // Add an Open with Browser entry.
-                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithBrowser(imageUrl);
-                    return false;
-                });
+            // Go back.
+            currentWebView.goBack();
+        } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
+            // Close the current tab.
+            closeCurrentTab();
+        } else {  // There isn't anything to do in Privacy Browser.
+            // Run the default commands.
+            super.onBackPressed();
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+            // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
+            System.exit(0);
         }
     }
 
+    // Process the results of a file browse.
     @Override
-    public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
-        // Get a handle for the bookmarks list view.
-        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // Run the commands that correlate to the specified request code.
+        switch (requestCode) {
+            case FILE_UPLOAD_REQUEST_CODE:
+                // File uploads only work on API >= 21.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    // Pass the file to the WebView.
+                    fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+                }
+                break;
 
-        // Get the views from the dialog fragment.
-        EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
-        EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+            case BROWSE_SAVE_WEBPAGE_IMAGE_REQUEST_CODE:
+                // Don't do anything if the user pressed back from the file picker.
+                if (resultCode == Activity.RESULT_OK) {
+                    // Get a handle for the save dialog fragment.
+                    DialogFragment saveWebpageImageDialogFragment= (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_as_image));
 
-        // Extract the strings from the edit texts.
-        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
-        String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
+                    // Only update the file name if the dialog still exists.
+                    if (saveWebpageImageDialogFragment != null) {
+                        // Get a handle for the save webpage image dialog.
+                        Dialog saveWebpageImageDialog = saveWebpageImageDialogFragment.getDialog();
 
-        // Create a favorite icon byte array output stream.
-        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+                        // Get a handle for the file name edit text.
+                        EditText fileNameEditText = saveWebpageImageDialog.findViewById(R.id.file_name_edittext);
 
-        // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-        favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+                        // Instantiate the file name helper.
+                        FileNameHelper fileNameHelper = new FileNameHelper();
 
-        // Convert the favorite icon byte array stream to a byte array.
-        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+                        // Convert the file name URI to a file name path.
+                        String fileNamePath = fileNameHelper.convertUriToFileNamePath(data.getData());
 
-        // Display the new bookmark below the current items in the (0 indexed) list.
-        int newBookmarkDisplayOrder = bookmarksListView.getCount();
+                        // Set the file name path as the text of the file name edit text.
+                        fileNameEditText.setText(fileNamePath);
+                    }
+                }
+                break;
+        }
+    }
 
-        // Create the bookmark.
-        bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
+    private void loadUrlFromTextBox() {
+        // Get a handle for the URL edit text.
+        EditText urlEditText = findViewById(R.id.url_edittext);
 
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+        // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
+        String unformattedUrlString = urlEditText.getText().toString().trim();
 
-        // Update the list view.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+        // Initialize the formatted URL string.
+        String url = "";
 
-        // Scroll to the new bookmark.
-        bookmarksListView.setSelection(newBookmarkDisplayOrder);
-    }
+        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
+        if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
+            // Load the entire content URL.
+            url = unformattedUrlString;
+        } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
+                unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
+            // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
+            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
+                unformattedUrlString = "https://" + unformattedUrlString;
+            }
 
-    @Override
-    public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
-        // Get a handle for the bookmarks list view.
-        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
+            // Initialize `unformattedUrl`.
+            URL unformattedUrl = null;
 
-        // Get handles for the views in the dialog fragment.
-        EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
-        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
-        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
+            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
+            try {
+                unformattedUrl = new URL(unformattedUrlString);
+            } catch (MalformedURLException e) {
+                e.printStackTrace();
+            }
 
-        // Get new folder name string.
-        String folderNameString = createFolderNameEditText.getText().toString();
+            // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
+            String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
+            String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
+            String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
+            String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
+            String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
 
-        // Create a folder icon bitmap.
-        Bitmap folderIconBitmap;
+            // Build the URI.
+            Uri.Builder uri = new Uri.Builder();
+            uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
 
-        // Set the folder icon bitmap according to the dialog.
-        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
-            // Get the default folder icon drawable.
-            Drawable folderIconDrawable = folderIconImageView.getDrawable();
+            // Decode the URI as a UTF-8 string in.
+            try {
+                url = URLDecoder.decode(uri.build().toString(), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                // Do nothing.  The formatted URL string will remain blank.
+            }
+        } else if (!unformattedUrlString.isEmpty()){  // This is not a URL, but rather a search string.
+            // Create an encoded URL String.
+            String encodedUrlString;
 
-            // Convert the folder icon drawable to a bitmap drawable.
-            BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+            // Sanitize the search input.
+            try {
+                encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
+            }
 
-            // Convert the folder icon bitmap drawable to a bitmap.
-            folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-        } else {  // Use the WebView favorite icon.
-            // Copy the favorite icon bitmap to the folder icon bitmap.
-            folderIconBitmap = favoriteIconBitmap;
+            // Add the base search URL.
+            url = searchURL + encodedUrlString;
         }
 
-        // Create a folder icon byte array output stream.
-        ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
+        // 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();
 
-        // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-        folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
+        // Make it so.
+        loadUrl(url);
+    }
 
-        // Convert the folder icon byte array stream to a byte array.
-        byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+    private void loadUrl(String url) {
+        // Sanitize the URL.
+        url = sanitizeUrl(url);
 
-        // Move all the bookmarks down one in the display order.
-        for (int i = 0; i < bookmarksListView.getCount(); i++) {
-            int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
-            bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
+        // Apply the domain settings.
+        applyDomainSettings(currentWebView, url, true, false);
+
+        // Load the URL.
+        currentWebView.loadUrl(url, customHeaders);
+    }
+
+    public void findPreviousOnPage(View view) {
+        // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
+        currentWebView.findNext(false);
+    }
+
+    public void findNextOnPage(View view) {
+        // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
+        currentWebView.findNext(true);
+    }
+
+    public void closeFindOnPage(View view) {
+        // Get a handle for the views.
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+        EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+
+        // Delete the contents of `find_on_page_edittext`.
+        findOnPageEditText.setText(null);
+
+        // Clear the highlighted phrases if the WebView is not null.
+        if (currentWebView != null) {
+            currentWebView.clearMatches();
         }
 
-        // Create the folder, which will be placed at the top of the `ListView`.
-        bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
+        // Hide the find on page linear layout.
+        findOnPageLinearLayout.setVisibility(View.GONE);
+
+        // Show the toolbar.
+        toolbar.setVisibility(View.VISIBLE);
+
+        // Get a handle for the input method manager.
+        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+        // Remove the lint warning below that the input method manager might be null.
+        assert inputMethodManager != null;
 
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+        // Hide the keyboard.
+        inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
+    }
 
-        // Scroll to the new folder.
-        bookmarksListView.setSelection(0);
+    @Override
+    public void onSaveWebpageImage(DialogFragment dialogFragment) {
+        // Get a handle for the file name edit text.
+        EditText fileNameEditText = dialogFragment.getDialog().findViewById(R.id.file_name_edittext);
+
+        // Get the file path string.
+        saveWebsiteImageFilePath = fileNameEditText.getText().toString();
+
+        // Check to see if the storage permission is needed.
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // The storage permission has been granted.
+            // Save the webpage image.
+            new SaveWebpageImage(this, currentWebView).execute(saveWebsiteImageFilePath);
+        } else {  // The storage permission has not been granted.
+            // Get the external private directory `File`.
+            File externalPrivateDirectoryFile = getExternalFilesDir(null);
+
+            // Remove the incorrect lint error below that the file might be null.
+            assert externalPrivateDirectoryFile != null;
+
+            // Get the external private directory string.
+            String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
+
+            // Check to see if the file path is in the external private directory.
+            if (saveWebsiteImageFilePath.startsWith(externalPrivateDirectory)) {  // The file path is in the external private directory.
+                // Save the webpage image.
+                new SaveWebpageImage(this, currentWebView).execute(saveWebsiteImageFilePath);
+            } else {  // The file path is in a public directory.
+                // Check if the user has previously denied the storage permission.
+                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                    // Instantiate the storage permission alert dialog.
+                    DialogFragment storagePermissionDialogFragment = new StoragePermissionDialog();
+
+                    // Show the storage permission alert dialog.  The permission will be requested when the dialog is closed.
+                    storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
+                } else {  // Show the permission request directly.
+                    // Request the write external storage permission.  The webpage image will be saved when it finishes.
+                    ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
+                }
+            }
+        }
     }
 
     @Override
-    public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) {
-        // Get handles for the views from `dialogFragment`.
-        EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
-        EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
-        RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
+    public void onCloseStoragePermissionDialog() {
+        // Request the write external storage permission.  The webpage image will be saved when it finishes.
+        ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
+    }
 
-        // Store the bookmark strings.
-        String bookmarkNameString = editBookmarkNameEditText.getText().toString();
-        String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
+    private void applyAppSettings() {
+        // Initialize the app if this is the first run.  This is done here instead of in `onCreate()` to shorten the time that an unthemed background is displayed on app startup.
+        if (webViewDefaultUserAgent == null) {
+            initializeApp();
+        }
 
-        // Update the bookmark.
-        if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
-            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
-        } else {  // Update the bookmark using the `WebView` favorite icon.
-            // Create a favorite icon byte array output stream.
-            ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
+        // Store the values from the shared preferences in variables.
+        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
+        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
+        sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
+        sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
+        sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
+        proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
+        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+        hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
+        scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
 
-            // Convert the favorite icon byte array stream to a byte array.
-            byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
+        // Get handles for the views that need to be modified.
+        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+        AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
+        ActionBar actionBar = getSupportActionBar();
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+        LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
-            //  Update the bookmark and the favorite icon.
-            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
+        // Remove the incorrect lint warning below that the action bar might be null.
+        assert actionBar != null;
+
+        // Apply the proxy through Orbot settings.
+        applyProxyThroughOrbot(false);
+
+        // Set Do Not Track status.
+        if (doNotTrackEnabled) {
+            customHeaders.put("DNT", "1");
+        } else {
+            customHeaders.remove("DNT");
         }
 
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+        // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
+        CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+        AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
+        AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
+        AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
 
-        // Update the list view.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
-    }
+        // Add the scrolling behavior to the layout parameters.
+        if (scrollAppBar) {
+            // Enable scrolling of the app bar.
+            swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+            toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+            findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+            tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+        } else {
+            // Disable scrolling of the app bar.
+            swipeRefreshLayoutParams.setBehavior(null);
+            toolbarLayoutParams.setScrollFlags(0);
+            findOnPageLayoutParams.setScrollFlags(0);
+            tabsLayoutParams.setScrollFlags(0);
 
-    @Override
-    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
-        // Get handles for the views from `dialogFragment`.
-        EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
-        RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
-        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
-        ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
+            // Expand the app bar if it is currently collapsed.
+            appBarLayout.setExpanded(true);
+        }
 
-        // Get the new folder name.
-        String newFolderNameString = editFolderNameEditText.getText().toString();
+        // Apply the modified layout parameters.
+        swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
+        toolbar.setLayoutParams(toolbarLayoutParams);
+        findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
+        tabsLinearLayout.setLayoutParams(tabsLayoutParams);
 
-        // Check if the favorite icon has changed.
-        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
-            // Update the name in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
-        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
-            // Create the new folder icon Bitmap.
-            Bitmap folderIconBitmap;
+        // 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);
 
-            // Populate the new folder icon bitmap.
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon drawable.
-                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
 
-                // Convert the folder icon drawable to a bitmap drawable.
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+            // 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);
 
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                // Copy the favorite icon bitmap to the folder icon bitmap.
-                folderIconBitmap = favoriteIconBitmap;
+                // Set the app bar scrolling.
+                nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
             }
+        }
 
-            // Create a folder icon byte array output stream.
-            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
+        // Update the full screen browsing mode settings.
+        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+            // Update the visibility of the app bar, which might have changed in the settings.
+            if (hideAppBar) {
+                // Hide the tab linear layout.
+                tabsLinearLayout.setVisibility(View.GONE);
 
-            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
+                // Hide the action bar.
+                actionBar.hide();
+            } else {
+                // Show the tab linear layout.
+                tabsLinearLayout.setVisibility(View.VISIBLE);
 
-            // Convert the folder icon byte array stream to a byte array.
-            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
+                // Show the action bar.
+                actionBar.show();
+            }
 
-            // Update the folder icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
-        } else {  // The folder icon and the name have changed.
-            // Get the new folder icon `Bitmap`.
-            Bitmap folderIconBitmap;
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon drawable.
-                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
+            // Hide the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                AdHelper.hideAd(findViewById(R.id.adview));
+            }
 
-                // Convert the folder icon drawable to a bitmap drawable.
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                // Copy the favorite icon bitmap to the folder icon bitmap.
-                folderIconBitmap = favoriteIconBitmap;
-            }
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else {  // Privacy Browser is not in full screen browsing mode.
+            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
+            inFullScreenBrowsingMode = false;
 
-            // Create a folder icon byte array output stream.
-            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
+            // Show the tab linear layout.
+            tabsLinearLayout.setVisibility(View.VISIBLE);
 
-            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
+            // Show the action bar.
+            actionBar.show();
 
-            // Convert the folder icon byte array stream to a byte array.
-            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
+            // Show the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
+                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
+            }
 
-            // Update the folder name and icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
+            // Remove the `SYSTEM_UI` flags from the root frame layout.
+            rootFrameLayout.setSystemUiVisibility(0);
+
+            // Add the translucent status flag.
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
         }
+    }
 
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+    private void initializeApp() {
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
-    }
+        // Get the theme preference.
+        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
 
-    @Override
-    public void onCloseDownloadLocationPermissionDialog(int downloadType) {
-        switch (downloadType) {
-            case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
-                // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
-                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                break;
+        // Get a handle for the input method.
+        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-            case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
-                // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
-                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                break;
-        }
-    }
+        // Remove the lint warning below that the input method manager might be null.
+        assert inputMethodManager != null;
 
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        // Get a handle for the fragment manager.
-        FragmentManager fragmentManager = getSupportFragmentManager();
+        // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
+        redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+        finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+
+        // Get handles for the URL views.
+        EditText urlEditText = findViewById(R.id.url_edittext);
 
-        switch (requestCode) {
-            case DOWNLOAD_FILE_REQUEST_CODE:
-                // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
-                DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
+        // Remove the formatting from the URL edit text when the user is editing the text.
+        urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
+            if (hasFocus) {  // The user is editing the URL text box.
+                // Remove the highlighting.
+                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.
+                urlEditText.setSelection(0);
 
-                // On API 23, displaying the fragment must be delayed or the app will crash.
-                if (Build.VERSION.SDK_INT == 23) {
-                    new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
-                } else {
-                    downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
-                }
+                // Reapply the highlighting.
+                highlightUrlText();
+            }
+        });
 
-                // Reset the download variables.
-                downloadUrl = "";
-                downloadContentDisposition = "";
-                downloadContentLength = 0;
-                break;
+        // Set the go button on the keyboard to load the URL in `urlTextBox`.
+        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.
+                loadUrlFromTextBox();
 
-            case DOWNLOAD_IMAGE_REQUEST_CODE:
-                // Show the download image alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
-                DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
+                // If the enter key was pressed, consume the event.
+                return true;
+            } else {
+                // If any other key was pressed, do not consume the event.
+                return false;
+            }
+        });
 
-                // On API 23, displaying the fragment must be delayed or the app will crash.
-                if (Build.VERSION.SDK_INT == 23) {
-                    new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
-                } else {
-                    downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
-                }
+        // Initialize the Orbot status and the waiting for Orbot trackers.
+        orbotStatus = "unknown";
+        waitingForOrbot = false;
 
-                // Reset the image URL variable.
-                downloadImageUrl = "";
-                break;
-        }
-    }
+        // Create an Orbot status `BroadcastReceiver`.
+        orbotStatusBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                // Store the content of the status message in `orbotStatus`.
+                orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
 
-    @Override
-    public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
-        // Download the image if it has an HTTP or HTTPS URI.
-        if (imageUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+                // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
+                if (orbotStatus.equals("ON") && waitingForOrbot) {
+                    // Reset the waiting for Orbot status.
+                    waitingForOrbot = false;
 
-            // Parse `imageUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
+                    // Get the intent that started the app.
+                    Intent launchingIntent = getIntent();
 
-            // Get a handle for the cookie manager.
-            CookieManager cookieManager = CookieManager.getInstance();
+                    // Get the information from the intent.
+                    String launchingIntentAction = launchingIntent.getAction();
+                    Uri launchingIntentUriData = launchingIntent.getData();
 
-            // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (cookieManager.acceptCookie()) {
-                // Get the cookies for `imageUrl`.
-                String cookies = cookieManager.getCookie(imageUrl);
+                    // If the intent action is a web search, perform the search.
+                    if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+                        // Create an encoded URL string.
+                        String encodedUrlString;
 
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
+                        // Sanitize the search input and convert it to a search.
+                        try {
+                            encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
+                        } catch (UnsupportedEncodingException exception) {
+                            encodedUrlString = "";
+                        }
+
+                        // Load the completed search URL.
+                        loadUrl(searchURL + encodedUrlString);
+                    } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
+                        // Load the URL from the intent.
+                        loadUrl(launchingIntentUriData.toString());
+                    } else {  // The is no URL in the intent.
+                        // Select the homepage based on the proxy through Orbot status.
+                        if (proxyThroughOrbot) {
+                            // Load the Tor homepage.
+                            loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
+                        } else {
+                            // Load the normal homepage.
+                            loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
+                        }
+                    }
+                }
             }
+        };
 
-            // Get the file name from the dialog fragment.
-            EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
-            String imageName = downloadImageNameEditText.getText().toString();
+        // Register `orbotStatusBroadcastReceiver` on `this` context.
+        this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
 
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
-            }
+        // Get handles for views that need to be modified.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        NavigationView navigationView = findViewById(R.id.navigationview);
+        TabLayout tabLayout = findViewById(R.id.tablayout);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+        ViewPager webViewPager = findViewById(R.id.webviewpager);
+        ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
+        FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
+        FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
+        FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
+        EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
 
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
+        // Listen for touches on the navigation menu.
+        navigationView.setNavigationItemSelectedListener(this);
 
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(imageUrl);
+        // Get handles for the navigation menu and the back and forward menu items.  The menu is zero-based.
+        Menu navigationMenu = navigationView.getMenu();
+        MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
+        MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
+        MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
+        MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5);
 
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+        // Update the web view pager every time a tab is modified.
+        webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+                // Do nothing.
+            }
 
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
+            @Override
+            public void onPageSelected(int position) {
+                // Close the find on page bar if it is open.
+                closeFindOnPage(null);
 
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The image is not an HTTP or HTTPS URI.
-            Snackbar.make(currentWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
-        }
-    }
+                // Set the current WebView.
+                setCurrentWebView(position);
 
-    @Override
-    public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
-        // Download the file if it has an HTTP or HTTPS URI.
-        if (downloadUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+                // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled via swiping in the view pager or by creating a new tab.
+                if (tabLayout.getSelectedTabPosition() != position) {
+                    // Create a handler to select the tab.
+                    Handler selectTabHandler = new Handler();
 
-            // Parse `downloadUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
+                    // Create a runnable to select the tab.
+                    Runnable selectTabRunnable = () -> {
+                        // Get a handle for the tab.
+                        TabLayout.Tab tab = tabLayout.getTabAt(position);
 
-            // Get a handle for the cookie manager.
-            CookieManager cookieManager = CookieManager.getInstance();
+                        // Assert that the tab is not null.
+                        assert tab != null;
 
-            // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (cookieManager.acceptCookie()) {
-                // Get the cookies for `downloadUrl`.
-                String cookies = cookieManager.getCookie(downloadUrl);
+                        // Select the tab.
+                        tab.select();
+                    };
 
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
+                    // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.
+                    selectTabHandler.postDelayed(selectTabRunnable, 150);
+                }
             }
 
-            // Get the file name from the dialog fragment.
-            EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
-            String fileName = downloadFileNameEditText.getText().toString();
-
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
+            @Override
+            public void onPageScrollStateChanged(int state) {
+                // Do nothing.
             }
+        });
 
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
+        // Display the View SSL Certificate dialog when the currently selected tab is reselected.
+        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+            @Override
+            public void onTabSelected(TabLayout.Tab tab) {
+                // Select the same page in the view pager.
+                webViewPager.setCurrentItem(tab.getPosition());
+            }
 
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(downloadUrl);
+            @Override
+            public void onTabUnselected(TabLayout.Tab tab) {
+                // Do nothing.
+            }
 
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+            @Override
+            public void onTabReselected(TabLayout.Tab tab) {
+                // Instantiate the View SSL Certificate dialog.
+                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
 
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
+                // Display the View SSL Certificate dialog.
+                viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
+            }
+        });
 
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The download is not an HTTP or HTTPS URI.
-            Snackbar.make(currentWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
+        // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
+        // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
+        if (darkTheme) {
+            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
+            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
+            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
+            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
+        } else {
+            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
+            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
+            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
+            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
         }
-    }
 
-    // Override `onBackPressed` to handle the navigation drawer and and the WebView.
-    @Override
-    public void onBackPressed() {
-        // Get a handle for the drawer layout and the tab layout.
-        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-        TabLayout tabLayout = findViewById(R.id.tablayout);
+        // Set the launch bookmarks activity FAB to launch the bookmarks activity.
+        launchBookmarksActivityFab.setOnClickListener(v -> {
+            // Get a copy of the favorite icon bitmap.
+            Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
 
-        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
-            // Close the navigation drawer.
-            drawerLayout.closeDrawer(GravityCompat.START);
-        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
-            if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
-                // close the bookmarks drawer.
-                drawerLayout.closeDrawer(GravityCompat.END);
-            } else {  // A subfolder is displayed.
-                // Place the former parent folder in `currentFolder`.
-                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
+            // Create a favorite icon byte array output stream.
+            ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Load the new folder.
-                loadBookmarksFolder();
-            }
-        } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
-            // Reset the current domain name so that navigation works if third-party requests are blocked.
-            currentWebView.resetCurrentDomainName();
+            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
 
-            // Set navigating history so that the domain settings are applied when the new URL is loaded.
-            currentWebView.setNavigatingHistory(true);
+            // Convert the favorite icon byte array stream to a byte array.
+            byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
 
-            // Go back.
-            currentWebView.goBack();
-        } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
-            // Close the current tab.
-            closeCurrentTab();
-        } else {  // There isn't anything to do in Privacy Browser.
-            // Run the default commands.
-            super.onBackPressed();
+            // Create an intent to launch the bookmarks activity.
+            Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
 
-            // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
-            System.exit(0);
-        }
-    }
+            // Add the extra information to the intent.
+            bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
+            bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
+            bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
+            bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
 
-    // Process the results of an upload file chooser.  Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // File uploads only work on API >= 21.
-        if (Build.VERSION.SDK_INT >= 21) {
-            // Pass the file to the WebView.
-            fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
-        }
-    }
+            // Make it so.
+            startActivity(bookmarksIntent);
+        });
 
-    private void loadUrlFromTextBox() {
-        // Get a handle for the URL edit text.
-        EditText urlEditText = findViewById(R.id.url_edittext);
+        // Set the create new bookmark folder FAB to display an alert dialog.
+        createBookmarkFolderFab.setOnClickListener(v -> {
+            // Create a create bookmark folder dialog.
+            DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
 
-        // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
-        String unformattedUrlString = urlEditText.getText().toString().trim();
+            // Show the create bookmark folder dialog.
+            createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
+        });
 
-        // Initialize the formatted URL string.
-        String url = "";
+        // Set the create new bookmark FAB to display an alert dialog.
+        createBookmarkFab.setOnClickListener(view -> {
+            // Instantiate the create bookmark dialog.
+            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
 
-        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
-        if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
-            // Load the entire content URL.
-            url = unformattedUrlString;
-        } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
-                unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
-            // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
-            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
-                unformattedUrlString = "https://" + unformattedUrlString;
-            }
+            // Display the create bookmark dialog.
+            createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
+        });
 
-            // Initialize `unformattedUrl`.
-            URL unformattedUrl = null;
+        // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
+        findOnPageEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // Do nothing.
+            }
 
-            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
-            try {
-                unformattedUrl = new URL(unformattedUrlString);
-            } catch (MalformedURLException e) {
-                e.printStackTrace();
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // Do nothing.
             }
 
-            // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
-            String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
-            String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
-            String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
-            String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
-            String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
+            @Override
+            public void afterTextChanged(Editable s) {
+                // Search for the text in the WebView if it is not null.  Sometimes on resume after a period of non-use the WebView will be null.
+                if (currentWebView != null) {
+                    currentWebView.findAllAsync(findOnPageEditText.getText().toString());
+                }
+            }
+        });
 
-            // Build the URI.
-            Uri.Builder uri = new Uri.Builder();
-            uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
+        // 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.
+                inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
 
-            // Decode the URI as a UTF-8 string in.
-            try {
-                url = URLDecoder.decode(uri.build().toString(), "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                // Do nothing.  The formatted URL string will remain blank.
+                // Consume the event.
+                return true;
+            } else {  // A different key was pressed.
+                // Do not consume the event.
+                return false;
             }
-        } else if (!unformattedUrlString.isEmpty()){  // This is not a URL, but rather a search string.
-            // Create an encoded URL String.
-            String encodedUrlString;
+        });
 
-            // Sanitize the search input.
-            try {
-                encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                encodedUrlString = "";
-            }
+        // Implement swipe to refresh.
+        swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
 
-            // Add the base search URL.
-            url = searchURL + encodedUrlString;
+        // Store the default progress view offsets for use later in `initializeWebView()`.
+        defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
+        defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
+
+        // Set the swipe to refresh color according to the theme.
+        if (darkTheme) {
+            swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
+            swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
+        } else {
+            swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
         }
 
-        // 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();
+        // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
+        drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
+        drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
 
-        // Make it so.
-        loadUrl(url);
-    }
+        // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
 
-    private void loadUrl(String url) {
-        // Sanitize the URL.
-        url = sanitizeUrl(url);
+        // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
+        currentBookmarksFolder = "";
 
-        // Apply the domain settings.
-        applyDomainSettings(currentWebView, url, true, false);
+        // Load the home folder, which is `""` in the database.
+        loadBookmarksFolder();
 
-        // Load the URL.
-        currentWebView.loadUrl(url, customHeaders);
-    }
+        bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
+            // Convert the id from long to int to match the format of the bookmarks database.
+            int databaseID = (int) id;
 
-    public void findPreviousOnPage(View view) {
-        // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
-        currentWebView.findNext(false);
-    }
+            // Get the bookmark cursor for this ID and move it to the first row.
+            Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
+            bookmarkCursor.moveToFirst();
 
-    public void findNextOnPage(View view) {
-        // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
-        currentWebView.findNext(true);
-    }
+            // Act upon the bookmark according to the type.
+            if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
+                // Store the new folder name in `currentBookmarksFolder`.
+                currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
 
-    public void closeFindOnPage(View view) {
-        // Get a handle for the views.
-        Toolbar toolbar = findViewById(R.id.toolbar);
-        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
-        EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+                // Load the new folder.
+                loadBookmarksFolder();
+            } else {  // The selected bookmark is not a folder.
+                // Load the bookmark URL.
+                loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
 
-        // Delete the contents of `find_on_page_edittext`.
-        findOnPageEditText.setText(null);
+                // Close the bookmarks drawer.
+                drawerLayout.closeDrawer(GravityCompat.END);
+            }
 
-        // Clear the highlighted phrases if the WebView is not null.
-        if (currentWebView != null) {
-            currentWebView.clearMatches();
-        }
+            // Close the `Cursor`.
+            bookmarkCursor.close();
+        });
 
-        // Hide the find on page linear layout.
-        findOnPageLinearLayout.setVisibility(View.GONE);
+        bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
+            // Convert the database ID from `long` to `int`.
+            int databaseId = (int) id;
 
-        // Show the toolbar.
-        toolbar.setVisibility(View.VISIBLE);
+            // Find out if the selected bookmark is a folder.
+            boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
 
-        // Get a handle for the input method manager.
-        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+            if (isFolder) {
+                // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
+                oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
 
-        // Remove the lint warning below that the input method manager might be null.
-        assert inputMethodManager != null;
+                // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
+                DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
+                editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
+            } else {
+                // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
+                DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
+                editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark));
+            }
 
-        // Hide the keyboard.
-        inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
-    }
+            // Consume the event.
+            return true;
+        });
 
-    private void applyAppSettings() {
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        // Get the status bar pixel size.
+        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
 
-        // Store the values from the shared preferences in variables.
-        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
-        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
-        sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
-        sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
-        sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
-        proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
-        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
-        hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
-        scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
+        // Get the resource density.
+        float screenDensity = getResources().getDisplayMetrics().density;
 
-        // Get handles for the views that need to be modified.
-        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-        AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
-        ActionBar actionBar = getSupportActionBar();
-        Toolbar toolbar = findViewById(R.id.toolbar);
-        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
-        LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+        // Calculate the drawer header padding.  This is used to move the text in the drawer headers below any cutouts.
+        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
 
-        // Remove the incorrect lint warning below that the action bar might be null.
-        assert actionBar != null;
+        // The drawer listener is used to update the navigation menu.`
+        drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
+            @Override
+            public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
+            }
 
-        // Apply the proxy through Orbot settings.
-        applyProxyThroughOrbot(false);
+            @Override
+            public void onDrawerOpened(@NonNull View drawerView) {
+            }
 
-        // Set Do Not Track status.
-        if (doNotTrackEnabled) {
-            customHeaders.put("DNT", "1");
-        } else {
-            customHeaders.remove("DNT");
-        }
+            @Override
+            public void onDrawerClosed(@NonNull View drawerView) {
+            }
 
-        // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
-        CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-        AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
-        AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
-        AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
+            @Override
+            public void onDrawerStateChanged(int newState) {
+                if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) {  // A drawer is opening or closing.
+                    // Get handles for the drawer headers.
+                    TextView navigationHeaderTextView = findViewById(R.id.navigationText);
+                    TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
 
-        // Add the scrolling behavior to the layout parameters.
-        if (scrollAppBar) {
-            // Enable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-            toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-        } else {
-            // Disable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(null);
-            toolbarLayoutParams.setScrollFlags(0);
-            findOnPageLayoutParams.setScrollFlags(0);
-            tabsLayoutParams.setScrollFlags(0);
+                    // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
+                    if (navigationHeaderTextView != null) {
+                        navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+                    }
 
-            // Expand the app bar if it is currently collapsed.
-            appBarLayout.setExpanded(true);
-        }
+                    // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
+                    if (bookmarksHeaderTextView != null) {
+                        bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+                    }
 
-        // Apply the modified layout parameters.
-        swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
-        toolbar.setLayoutParams(toolbarLayoutParams);
-        findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
-        tabsLinearLayout.setLayoutParams(tabsLayoutParams);
+                    // Update the navigation menu items if the WebView is not null.
+                    if (currentWebView != null) {
+                        navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
+                        navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
+                        navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
+                        navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
 
-        // 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);
+                        // Hide the keyboard (if displayed).
+                        inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+                    }
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                    // 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.
+                    urlEditText.clearFocus();
+                    currentWebView.clearFocus();
+                }
+            }
+        });
 
-            // 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);
+        // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
+        customHeaders.put("X-Requested-With", "");
 
-                // Set the app bar scrolling.
-                nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
-            }
-        }
+        // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
+        @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
 
-        // Update the full screen browsing mode settings.
-        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-            // Update the visibility of the app bar, which might have changed in the settings.
-            if (hideAppBar) {
-                // Hide the tab linear layout.
-                tabsLinearLayout.setVisibility(View.GONE);
+        // Get a handle for the WebView.
+        WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
 
-                // Hide the action bar.
-                actionBar.hide();
-            } else {
-                // Show the tab linear layout.
-                tabsLinearLayout.setVisibility(View.VISIBLE);
+        // Store the default user agent.
+        webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
 
-                // Show the action bar.
-                actionBar.show();
-            }
+        // Destroy the bare WebView.
+        bareWebView.destroy();
+    }
 
-            // Hide the banner ad in the free flavor.
-            if (BuildConfig.FLAVOR.contentEquals("free")) {
-                AdHelper.hideAd(findViewById(R.id.adview));
-            }
+    @Override
+    public void navigateHistory(String url, int steps) {
+        // Reset the current domain name so that navigation works if third-party requests are blocked.
+        currentWebView.resetCurrentDomainName();
 
-            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        // Apply the domain settings.
+        applyDomainSettings(currentWebView, url, false, false);
 
-            /* Hide the system bars.
-             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-        } else {  // Privacy Browser is not in full screen browsing mode.
-            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
-            inFullScreenBrowsingMode = false;
+        // Load the history entry.
+        currentWebView.goBackOrForward(steps);
+    }
 
-            // Show the tab linear layout.
-            tabsLinearLayout.setVisibility(View.VISIBLE);
+    @Override
+    public void pinnedErrorGoBack() {
+        // Get the current web back forward list.
+        WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
 
-            // Show the action bar.
-            actionBar.show();
+        // Get the previous entry URL.
+        String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
 
-            // Show the banner ad in the free flavor.
-            if (BuildConfig.FLAVOR.contentEquals("free")) {
-                // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
-                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
-            }
+        // Reset the current domain name so that navigation works if third-party requests are blocked.
+        currentWebView.resetCurrentDomainName();
 
-            // Remove the `SYSTEM_UI` flags from the root frame layout.
-            rootFrameLayout.setSystemUiVisibility(0);
+        // Apply the domain settings.
+        applyDomainSettings(currentWebView, previousUrl, false, false);
 
-            // Add the translucent status flag.
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-        }
+        // Go back.
+        currentWebView.goBack();
     }
 
-
     // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
     @SuppressLint("SetJavaScriptEnabled")
     private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
         // Store a copy of the current user agent to track changes for the return boolean.
         String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
 
+        // Store the current URL.
+        nestedScrollWebView.setCurrentUrl(url);
+
         // Parse the URL into a URI.
         Uri uri = Uri.parse(url);
 
@@ -3356,15 +3870,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
                 // Form data can be removed once the minimum API >= 26.
                 boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST,
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST,
                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY,
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY,
                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY,
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1);
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY,
                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
@@ -3578,11 +4093,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
                 nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
                 boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, sharedPreferences.getBoolean("easylist", true));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true));
+                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
                 nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
 
@@ -3971,8 +4487,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Flag the intent to open in a new task.
         openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-        // Show the chooser.
-        startActivity(openWithAppIntent);
+        try {
+            // Show the chooser.
+            startActivity(openWithAppIntent);
+        } catch (ActivityNotFoundException exception) {
+            // Show a snackbar with the error.
+            Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
+        }
     }
 
     private void openWithBrowser(String url) {
@@ -3985,8 +4506,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Flag the intent to open in a new task.
         openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-        // Show the chooser.
-        startActivity(openWithBrowserIntent);
+        try {
+            // Show the chooser.
+            startActivity(openWithBrowserIntent);
+        } catch (ActivityNotFoundException exception) {
+            // Show a snackbar with the error.
+            Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
+        }
     }
 
     private String sanitizeUrl(String url) {
@@ -4014,6 +4540,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             if (url.contains("&fbclid=")) {
                 url = url.substring(0, url.indexOf("&fbclid="));
             }
+
+            // Remove `?fbadid=`.
+            if (url.contains("?fbadid=")) {
+                url = url.substring(0, url.indexOf("?fbadid="));
+            }
+
+            // Remove `&fbadid=`.
+            if (url.contains("&fbadid=")) {
+                url = url.substring(0, url.indexOf("&fbadid="));
+            }
         }
 
         // Sanitize Twitter AMP redirects.
@@ -4028,12 +4564,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         return url;
     }
 
+    public void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
+        // Store the blocklists.
+        easyList = combinedBlocklists.get(0);
+        easyPrivacy = combinedBlocklists.get(1);
+        fanboysAnnoyanceList = combinedBlocklists.get(2);
+        fanboysSocialList = combinedBlocklists.get(3);
+        ultraList = combinedBlocklists.get(4);
+        ultraPrivacy = combinedBlocklists.get(5);
+
+        // Add the first tab.
+        addNewTab("", true);
+    }
+
     public void addTab(View view) {
         // Add a new tab with a blank URL.
-        addNewTab("");
+        addNewTab("", true);
     }
 
-    private void addNewTab(String url) {
+    private void addNewTab(String url, boolean moveToTab) {
         // Sanitize the URL.
         url = sanitizeUrl(url);
 
@@ -4057,7 +4606,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         newTab.setCustomView(R.layout.tab_custom_view);
 
         // Add the new WebView page.
-        webViewPagerAdapter.addPage(newTabNumber, webViewPager, url);
+        webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
     }
 
     public void closeTab(View view) {
@@ -4283,7 +4832,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         EditText urlEditText = findViewById(R.id.url_edittext);
         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
-        //Stop the swipe to refresh indicator if it is running
+        // Stop the swipe to refresh indicator if it is running
         swipeRefreshLayout.setRefreshing(false);
 
         // Get the WebView tab fragment.
@@ -4293,19 +4842,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         View fragmentView = webViewTabFragment.getView();
 
         // Set the current WebView if the fragment view is not null.
-        if (fragmentView != null) {
+        if (fragmentView != null) {  // The fragment has been populated.
             // Store the current WebView.
             currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
             // Update the status of swipe to refresh.
             if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
-                if (Build.VERSION.SDK_INT >= 23) {  // For API >= 23, swipe refresh layout is continuously updated with an on scroll change listener and only enabled if the WebView is scrolled to the top.
-                    // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.
-                    swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
-                } else {
-                    // Enable the swipe refresh layout.
-                    swipeRefreshLayout.setEnabled(true);
-                }
+                // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.  It is updated every time the scroll changes.
+                swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
             } else {  // Swipe to refresh is disabled.
                 // Disable the swipe refresh layout.
                 swipeRefreshLayout.setEnabled(false);
@@ -4369,6 +4913,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             } else {
                 urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
             }
+        } else {  // The fragment has not been populated.  Try again in 100 milliseconds.
+            // Create a handler to set the current WebView.
+            Handler setCurrentWebViewHandler = new Handler();
+
+            // Create a runnable to set the current WebView.
+            Runnable setCurrentWebWebRunnable = () -> {
+                // Set the current WebView.
+                setCurrentWebView(pageNumber);
+            };
+
+            // Try setting the current WebView again after 100 milliseconds.
+            setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100);
         }
     }
 
@@ -4394,7 +4950,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
         // Instantiate the blocklist helper.
-        BlockListHelper blockListHelper = new BlockListHelper();
+        BlocklistHelper blocklistHelper = new BlocklistHelper();
 
         // Remove the lint warning below that the input method manager might be null.
         assert inputMethodManager != null;
@@ -4402,9 +4958,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get the relevant preferences.
-        boolean downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
-
         // Initialize the favorite icon.
         nestedScrollWebView.initializeFavoriteIcon();
 
@@ -4525,7 +5078,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Allow the downloading of files.
         nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
             // Check if the download should be processed by an external app.
-            if (downloadWithExternalApp) {  // Download with an external app.
+            if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
                 // Create a download intent.  Not specifying the action type will display the maximum number of options.
                 Intent downloadIntent = new Intent();
 
@@ -4591,13 +5144,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
-        // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.
+        // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.  Also reinforce full screen browsing mode.
         // Once the minimum API >= 23 this can be replaced with `nestedScrollWebView.setOnScrollChangeListener()`.
         nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
             if (nestedScrollWebView.getSwipeToRefresh()) {
                 // Only enable swipe to refresh if the WebView is scrolled to the top.
                 swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
             }
+
+            // Reinforce the system UI visibility flags if in full screen browsing mode.
+            // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
+            if (inFullScreenBrowsingMode) {
+                /* Hide the system bars.
+                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                 */
+                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+            }
         });
 
         // Set the web chrome client.
@@ -4757,6 +5323,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Show the full screen video frame layout.
                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
+
+                // Disable the screen timeout while the video is playing.  YouTube does this automatically, but not all other videos do.
+                fullScreenVideoFrameLayout.setKeepScreenOn(true);
             }
 
             // Exit full screen video.
@@ -4765,6 +5334,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get a handle for the full screen video frame layout.
                 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
 
+                // Re-enable the screen timeout.
+                fullScreenVideoFrameLayout.setKeepScreenOn(false);
+
                 // Unset the full screen video flag.
                 displayingFullScreenVideo = false;
 
@@ -4833,8 +5405,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Create an intent to open a chooser based ont the file chooser parameters.
                     Intent fileChooserIntent = fileChooserParams.createIntent();
 
-                    // Open the file chooser.  Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
-                    startActivityForResult(fileChooserIntent, 0);
+                    // Open the file chooser.
+                    startActivityForResult(fileChooserIntent, FILE_UPLOAD_REQUEST_CODE);
                 }
                 return true;
             }
@@ -4921,6 +5493,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
             @Override
             public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+                // Check to see if the resource request is for the main URL.
+                if (url.equals(nestedScrollWebView.getCurrentUrl())) {
+                    // `return null` loads the resource request, which should never be blocked if it is the main URL.
+                    return null;
+                }
+
+                // Wait until the blocklists have been populated.  When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately.
+                while (ultraPrivacy == null) {
+                    // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown.
+                    synchronized (this) {
+                        try {
+                            // Check to see if the blocklists have been populated after 100 ms.
+                            wait(100);
+                        } catch (InterruptedException exception) {
+                            // Do nothing.
+                        }
+                    }
+                }
+
                 // Sanitize the URL.
                 url = sanitizeUrl(url);
 
@@ -4984,7 +5575,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Block third-party requests if enabled.
                 if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
                     // Add the result to the resource requests.
-                    nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_THIRD_PARTY, url});
+                    nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url});
 
                     // Increment the blocked requests counters.
                     nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
@@ -5010,20 +5601,60 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     return emptyWebResourceResponse;
                 }
 
+                // Check UltraList if it is enabled.
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) {
+                    // Check the URL against UltraList.
+                    String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
+
+                    // Process the UltraList results.
+                    if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraLists's blacklist.
+                        // Add the result to the resource requests.
+                        nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
+
+                        // Increment the blocked requests counters.
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST);
+
+                        // Update the titles of the blocklist menu items if the WebView is currently displayed.
+                        if (webViewDisplayed) {
+                            // Updating the UI must be run from the UI thread.
+                            activity.runOnUiThread(() -> {
+                                // Update the menu item titles.
+                                navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+                                // Update the options menu if it has been populated.
+                                if (optionsMenu != null) {
+                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsMenu.findItem(R.id.ultralist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
+                                }
+                            });
+                        }
+
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraList's whitelist.
+                        // Add a whitelist entry to the resource requests array.
+                        nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
+
+                        // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
+                        return null;
+                    }
+                }
+
                 // Check UltraPrivacy if it is enabled.
-                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)) {
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) {
                     // Check the URL against UltraPrivacy.
-                    String[] ultraPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
+                    String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
 
                     // Process the UltraPrivacy results.
-                    if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraPrivacy's blacklist.
+                    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);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY);
 
                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
                         if (webViewDisplayed) {
@@ -5035,14 +5666,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
                                     optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
+                                    optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
                                 }
                             });
                         }
 
                         // The resource request was blocked.  Return an empty web resource response.
                         return emptyWebResourceResponse;
-                    } else if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraPrivacy's whitelist.
+                    } else if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraPrivacy's whitelist.
                         // Add a whitelist entry to the resource requests array.
                         nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
                                 ultraPrivacyResults[5]});
@@ -5053,18 +5684,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Check EasyList if it is enabled.
-                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)) {
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) {
                     // Check the URL against EasyList.
-                    String[] easyListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
+                    String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
 
                     // Process the EasyList results.
-                    if (easyListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyList's blacklist.
+                    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]});
 
                         // Increment the blocked requests counters.
                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
-                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST);
 
                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
                         if (webViewDisplayed) {
@@ -5076,33 +5707,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
                                     optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
+                                    optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + 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.
+                    } 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 (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)) {
+                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) {
                     // Check the URL against EasyPrivacy.
-                    String[] easyPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
+                    String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
 
                     // Process the EasyPrivacy results.
-                    if (easyPrivacyResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyPrivacy's blacklist.
+                    if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyPrivacy's blacklist.
                         // Add the result to the resource requests.
                         nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
                                 easyPrivacyResults[5]});
 
                         // Increment the blocked requests counters.
                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
-                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY);
+                        nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY);
 
                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
                         if (webViewDisplayed) {
@@ -5114,14 +5745,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
                                     optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
+                                    optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + 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.
+                    } 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]};
                     }
@@ -5130,10 +5761,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Check Fanboy’s Annoyance List if it is enabled.
                 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
                     // Check the URL against Fanboy's Annoyance List.
-                    String[] fanboysAnnoyanceListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
+                    String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
 
                     // Process the Fanboy's Annoyance List results.
-                    if (fanboysAnnoyanceListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Annoyance List's blacklist.
+                    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]});
@@ -5160,17 +5791,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // 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.
+                    } else if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)){  // The resource request matched Fanboy's Annoyance List's whitelist.
                         // Update the whitelist result string array tracker.
                         whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
                                 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
                     }
                 } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
                     // Check the URL against Fanboy's Annoyance List.
-                    String[] fanboysSocialListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
+                    String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
 
                     // Process the Fanboy's Social Blocking List results.
-                    if (fanboysSocialListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Social Blocking List's blacklist.
+                    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]});
@@ -5197,7 +5828,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // 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.
+                    } 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]};
@@ -5208,7 +5839,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 if (whitelistResultStringArray != null) {  // The request was processed by a whitelist.
                     nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
                 } else {  // The request didn't match any blocklist entry.  Log it as a default request.
-                    nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_DEFAULT, url});
+                    nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_DEFAULT, url});
                 }
 
                 // The resource request has not been blocked.  `return null` loads the requested resource.
@@ -5297,20 +5928,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Get the IP addresses for the host.
                     new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
 
-                    // Apply any custom domain settings if the URL was loaded by navigating history.
-                    if (nestedScrollWebView.getNavigatingHistory()) {
-                        // Reset navigating history.
-                        nestedScrollWebView.setNavigatingHistory(false);
-
-                        // Apply the domain settings.
-                        boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
-
-                        // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
-                        if (userAgentChanged) {
-                            loadUrl(url);
-                        }
-                    }
-
                     // Replace Refresh with Stop if the options menu has been created.  (The first WebView typically begins loading before the menu items are instantiated.)
                     if (optionsMenu != null) {
                         // Get a handle for the refresh menu item.
@@ -5438,11 +6055,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 tabTitleTextView.setText(R.string.new_tab);
                             }
                         } else {  // The WebView has loaded a webpage.
-                            // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
-                            urlEditText.setText(currentUrl);
+                            // Update the URL edit text if it is not currently being edited.
+                            if (!urlEditText.hasFocus()) {
+                                // Sanitize the current URL.  This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared.
+                                String sanitizedUrl = sanitizeUrl(currentUrl);
 
-                            // Apply text highlighting to the URL.
-                            highlightUrlText();
+                                // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
+                                urlEditText.setText(sanitizedUrl);
+
+                                // Apply text highlighting to the URL.
+                                highlightUrlText();
+                            }
 
                             // Only populate the title text view if the tab has been fully created.
                             if (tab != null) {