seb-win-refactoring/SafeExamBrowser.Browser/BrowserWindow.cs

788 lines
26 KiB
C#

/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.WinForms.Handler;
using CefSharp.WinForms.Host;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Browser.Wrapper;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using Syroot.Windows.IO;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
using DisplayHandler = SafeExamBrowser.Browser.Handlers.DisplayHandler;
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
using TitleChangedEventHandler = SafeExamBrowser.Applications.Contracts.Events.TitleChangedEventHandler;
namespace SafeExamBrowser.Browser
{
internal class BrowserWindow : Contracts.IBrowserWindow
{
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
private const double ZOOM_FACTOR = 0.2;
private readonly AppConfig appConfig;
private readonly Clipboard clipboard;
private readonly IFileSystemDialog fileSystemDialog;
private readonly IHashAlgorithm hashAlgorithm;
private readonly HttpClient httpClient;
private readonly IKeyGenerator keyGenerator;
private readonly IModuleLogger logger;
private readonly IMessageBox messageBox;
private readonly SessionMode sessionMode;
private readonly Dictionary<int, BrowserWindow> popups;
private readonly BrowserSettings settings;
private readonly string startUrl;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
private (string term, bool isInitial, bool caseSensitive, bool forward) findParameters;
private IBrowserWindow window;
private double zoomLevel;
private WindowSettings WindowSettings
{
get { return IsMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
}
internal IBrowserControl Control { get; private set; }
internal int Id { get; }
public IntPtr Handle { get; private set; }
public IconResource Icon { get; private set; }
public bool IsMainWindow { get; private set; }
public string Title { get; private set; }
public string Url { get; private set; }
internal event WindowClosedEventHandler Closed;
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
internal event PopupRequestedEventHandler PopupRequested;
internal event ResetRequestedEventHandler ResetRequested;
internal event TerminationRequestedEventHandler TerminationRequested;
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
public event IconChangedEventHandler IconChanged;
public event TitleChangedEventHandler TitleChanged;
public BrowserWindow(
AppConfig appConfig,
Clipboard clipboard,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
int id,
bool isMainWindow,
IKeyGenerator keyGenerator,
IModuleLogger logger,
IMessageBox messageBox,
SessionMode sessionMode,
BrowserSettings settings,
string startUrl,
IText text,
IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.clipboard = clipboard;
this.fileSystemDialog = fileSystemDialog;
this.hashAlgorithm = hashAlgorithm;
this.httpClient = new HttpClient();
this.Id = id;
this.IsMainWindow = isMainWindow;
this.keyGenerator = keyGenerator;
this.logger = logger;
this.messageBox = messageBox;
this.popups = new Dictionary<int, BrowserWindow>();
this.sessionMode = sessionMode;
this.settings = settings;
this.startUrl = startUrl;
this.text = text;
this.uiFactory = uiFactory;
}
public void Activate()
{
window.BringToForeground();
}
internal void Close()
{
window.Close();
Control.Destroy();
}
internal void Focus(bool forward)
{
if (forward)
{
window.FocusToolbar(forward);
}
else
{
window.FocusBrowser();
Activate();
}
}
internal void InitializeControl()
{
var cefSharpControl = default(ICefSharpControl);
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} #{Id}");
var dialogHandler = new DialogHandler();
var displayHandler = new DisplayHandler();
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
var downloadHandler = new DownloadHandler(appConfig, downloadLogger, settings, WindowSettings);
var keyboardHandler = new KeyboardHandler();
var renderHandler = new RenderProcessMessageHandler(appConfig, clipboard, keyGenerator, settings, text);
var requestFilter = new RequestFilter();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
var resourceHandler = new ResourceHandler(appConfig, requestFilter, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings);
Icon = new BrowserIconResource();
if (IsMainWindow)
{
cefSharpControl = new CefSharpBrowserControl(CreateLifeSpanHandlerForMainWindow(), startUrl);
}
else
{
cefSharpControl = new CefSharpPopupControl();
}
dialogHandler.DialogRequested += DialogHandler_DialogRequested;
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
downloadHandler.DownloadAborted += DownloadHandler_DownloadAborted;
downloadHandler.DownloadUpdated += DownloadHandler_DownloadUpdated;
keyboardHandler.FindRequested += KeyboardHandler_FindRequested;
keyboardHandler.FocusAddressBarRequested += KeyboardHandler_FocusAddressBarRequested;
keyboardHandler.HomeNavigationRequested += HomeNavigationRequested;
keyboardHandler.ReloadRequested += ReloadRequested;
keyboardHandler.TabPressed += KeyboardHandler_TabPressed;
keyboardHandler.ZoomInRequested += ZoomInRequested;
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
resourceHandler.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
InitializeRequestFilter(requestFilter);
Control = new BrowserControl(clipboard, cefSharpControl, dialogHandler, displayHandler, downloadHandler, keyboardHandler, controlLogger, renderHandler, requestHandler);
Control.AddressChanged += Control_AddressChanged;
Control.LoadFailed += Control_LoadFailed;
Control.LoadingStateChanged += Control_LoadingStateChanged;
Control.TitleChanged += Control_TitleChanged;
Control.Initialize();
logger.Debug("Initialized browser control.");
}
internal void InitializeWindow()
{
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, this.logger);
window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.Closed += Window_Closed;
window.Closing += Window_Closing;
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested;
window.LoseFocusRequested += Window_LoseFocusRequested;
window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested;
window.ZoomResetRequested += ZoomResetRequested;
window.UpdateZoomLevel(CalculateZoomPercentage());
window.Show();
window.BringToForeground();
Handle = window.Handle;
logger.Debug("Initialized browser window.");
}
private ILifeSpanHandler CreateLifeSpanHandlerForMainWindow()
{
return LifeSpanHandler
.Create(() => LifeSpanHandler_CreatePopup())
.OnBeforePopupCreated((wb, b, f, u, t, d, g, s) => LifeSpanHandler_PopupRequested(u))
.OnPopupCreated((c, u) => LifeSpanHandler_PopupCreated(c))
.OnPopupDestroyed((c, b) => LifeSpanHandler_PopupDestroyed(c))
.Build();
}
private void InitializeRequestFilter(IRequestFilter requestFilter)
{
if (settings.Filter.ProcessContentRequests || settings.Filter.ProcessMainRequests)
{
var factory = new RuleFactory();
foreach (var settings in settings.Filter.Rules)
{
var rule = factory.CreateRule(settings.Type);
rule.Initialize(settings);
requestFilter.Load(rule);
}
logger.Debug($"Initialized request filter with {settings.Filter.Rules.Count} rule(s).");
if (requestFilter.Process(new Request { Url = settings.StartUrl }) != FilterResult.Allow)
{
var rule = factory.CreateRule(FilterRuleType.Simplified);
rule.Initialize(new FilterRuleSettings { Expression = settings.StartUrl, Result = FilterResult.Allow });
requestFilter.Load(rule);
logger.Debug($"Automatically created filter rule to allow start URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{settings.StartUrl}'" : "")}.");
}
}
}
private void Control_AddressChanged(string address)
{
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
Url = address;
window.UpdateAddress(address);
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
{
Title = address;
window.UpdateTitle(address);
TitleChanged?.Invoke(address);
}
AutoFind();
}
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
{
switch (errorCode)
{
case (int) CefErrorCode.Aborted:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
break;
case (int) CefErrorCode.InternetDisconnected:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has failed due to loss of internet connection.");
break;
case (int) CefErrorCode.None:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
break;
case (int) CefErrorCode.UnknownUrlScheme:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has an unknown URL scheme and will be handled by the OS.");
break;
default:
HandleUnknownLoadFailure(errorCode, errorText, isMainRequest, url);
break;
}
}
private void HandleUnknownLoadFailure(int errorCode, string errorText, bool isMainRequest, string url)
{
var requestInfo = $"{errorText} ({errorCode}, {(isMainRequest ? "main" : "resource")} request)";
logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {requestInfo}.");
if (isMainRequest)
{
var title = text.Get(TextKey.Browser_LoadErrorTitle);
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {requestInfo}";
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => Control.NavigateBackwards());
}
}
private void Control_LoadingStateChanged(bool isLoading)
{
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && Control.CanNavigateBackwards;
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && Control.CanNavigateForwards;
window.UpdateLoadingState(isLoading);
}
private void Control_TitleChanged(string title)
{
if (WindowSettings.UrlPolicy != UrlPolicy.Always)
{
Title = title;
window.UpdateTitle(Title);
TitleChanged?.Invoke(Title);
}
}
private void DialogHandler_DialogRequested(DialogRequestedEventArgs args)
{
var isDownload = args.Operation == FileSystemOperation.Save;
var isUpload = args.Operation == FileSystemOperation.Open;
var isAllowed = (isDownload && settings.AllowDownloads) || (isUpload && settings.AllowUploads);
var initialPath = default(string);
if (isDownload)
{
initialPath = args.InitialPath;
}
else if (string.IsNullOrEmpty(settings.DownAndUploadDirectory))
{
initialPath = KnownFolders.Downloads.ExpandedPath;
}
else
{
initialPath = Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
}
if (isAllowed)
{
var result = fileSystemDialog.Show(
args.Element,
args.Operation,
initialPath,
title: args.Title,
parent: window,
restrictNavigation: !settings.AllowCustomDownAndUploadLocation,
showElementPath: settings.ShowFileSystemElementPath);
if (result.Success)
{
args.FullPath = result.FullPath;
args.Success = result.Success;
logger.Debug($"User selected path '{result.FullPath}' when asked to {args.Operation}->{args.Element}.");
}
else
{
logger.Debug($"User aborted file system dialog to {args.Operation}->{args.Element}.");
}
}
else
{
logger.Info($"Blocked file system dialog to {args.Operation}->{args.Element}, as {(isDownload ? "downloading" : "uploading")} is not allowed.");
ShowDownUploadNotAllowedMessage(isDownload);
}
}
private void DisplayHandler_FaviconChanged(string uri)
{
Task.Run(() =>
{
var request = new HttpRequestMessage(HttpMethod.Head, uri);
var response = httpClient.SendAsync(request).ContinueWith(task =>
{
if (task.IsCompleted && task.Result.IsSuccessStatusCode)
{
Icon = new BrowserIconResource(uri);
IconChanged?.Invoke(Icon);
window.UpdateIcon(Icon);
}
});
});
}
private void DisplayHandler_ProgressChanged(double value)
{
window.UpdateProgress(value);
}
private void DownloadHandler_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
{
if (settings.AllowConfigurationDownloads)
{
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
ConfigurationDownloadRequested?.Invoke(fileName, args);
if (args.AllowDownload)
{
logger.Debug($"Download request for configuration file '{fileName}' was granted.");
}
else
{
logger.Debug($"Download request for configuration file '{fileName}' was denied.");
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle, parent: window);
}
}
else
{
logger.Debug($"Discarded download request for configuration file '{fileName}'.");
}
}
private void DownloadHandler_DownloadAborted()
{
ShowDownUploadNotAllowedMessage();
}
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
{
window.UpdateDownloadState(state);
}
private void HomeNavigationRequested()
{
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
{
var navigate = false;
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
if (settings.HomeNavigationRequiresPassword && !string.IsNullOrWhiteSpace(settings.HomePasswordHash))
{
var message = text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequired);
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequiredTitle);
var dialog = uiFactory.CreatePasswordDialog(message, title);
var result = dialog.Show(window);
if (result.Success)
{
var passwordHash = hashAlgorithm.GenerateHashFor(result.Password);
if (settings.HomePasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase))
{
navigate = true;
}
else
{
messageBox.Show(TextKey.MessageBox_InvalidHomePassword, TextKey.MessageBox_InvalidHomePasswordTitle, icon: MessageBoxIcon.Warning, parent: window);
}
}
}
else
{
var message = text.Get(TextKey.MessageBox_BrowserHomeQuestion);
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.MessageBox_BrowserHomeQuestionTitle);
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
navigate = result == MessageBoxResult.Yes;
}
if (navigate)
{
Control.NavigateTo(url);
}
}
}
private void KeyboardHandler_FindRequested()
{
if (settings.AllowFind)
{
window.ShowFindbar();
}
}
private void KeyboardHandler_FocusAddressBarRequested()
{
window.FocusAddressBar();
}
private void KeyboardHandler_TabPressed(bool shiftPressed)
{
Control.ExecuteJavaScript("document.activeElement.tagName", result =>
{
if (result.Result is string tagName && tagName?.ToUpper() == "BODY")
{
// This means the user is now at the start of the focus / tabIndex chain in the website.
if (shiftPressed)
{
window.FocusToolbar(!shiftPressed);
}
else
{
LoseFocusRequested?.Invoke(true);
}
}
});
}
private ChromiumHostControl LifeSpanHandler_CreatePopup()
{
var args = new PopupRequestedEventArgs();
PopupRequested?.Invoke(args);
var control = args.Window.Control.EmbeddableControl as ChromiumHostControl;
var id = control.GetHashCode();
var window = args.Window;
popups[id] = window;
window.Closed += (_) => popups.Remove(id);
return control;
}
private void LifeSpanHandler_PopupCreated(ChromiumHostControl control)
{
var id = control.GetHashCode();
var window = popups[id];
window.InitializeWindow();
}
private void LifeSpanHandler_PopupDestroyed(ChromiumHostControl control)
{
var id = control.GetHashCode();
var window = popups[id];
window.Close();
}
private PopupCreation LifeSpanHandler_PopupRequested(string targetUrl)
{
var creation = PopupCreation.Cancel;
var validCurrentUri = Uri.TryCreate(Control.Address, UriKind.Absolute, out var currentUri);
var validNewUri = Uri.TryCreate(targetUrl, UriKind.Absolute, out var newUri);
var sameHost = validCurrentUri && validNewUri && string.Equals(currentUri.Host, newUri.Host, StringComparison.OrdinalIgnoreCase);
switch (settings.PopupPolicy)
{
case PopupPolicy.Allow:
case PopupPolicy.AllowSameHost when sameHost:
logger.Debug($"Forwarding request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}...");
creation = PopupCreation.Continue;
break;
case PopupPolicy.AllowSameWindow:
case PopupPolicy.AllowSameHostAndWindow when sameHost:
logger.Info($"Discarding request to open new window and loading{(WindowSettings.UrlPolicy.CanLog() ? $" '{targetUrl}'" : "")} directly...");
Control.NavigateTo(targetUrl);
break;
case PopupPolicy.AllowSameHost when !sameHost:
case PopupPolicy.AllowSameHostAndWindow when !sameHost:
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")} as it targets a different host.");
break;
default:
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}.");
break;
}
return creation;
}
private void RequestHandler_QuitUrlVisited(string url)
{
Task.Run(() =>
{
if (settings.ResetOnQuitUrl)
{
logger.Info("Forwarding request to reset browser...");
ResetRequested?.Invoke();
}
else
{
if (settings.ConfirmQuitUrl)
{
var message = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmation);
var title = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmationTitle);
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
var terminate = result == MessageBoxResult.Yes;
if (terminate)
{
logger.Info($"User confirmed termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}, forwarding request...");
TerminationRequested?.Invoke();
}
else
{
logger.Info($"User aborted termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}.");
}
}
else
{
logger.Info($"Automatically requesting termination due to quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}...");
TerminationRequested?.Invoke();
}
}
});
}
private void RequestHandler_RequestBlocked(string url)
{
Task.Run(() =>
{
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "");
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
Control.TitleChanged -= Control_TitleChanged;
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
{
window.UpdateTitle($"*** {title} ***");
TitleChanged?.Invoke($"*** {title} ***");
}
messageBox.Show(message, title, parent: window);
Control.TitleChanged += Control_TitleChanged;
});
}
private void ReloadRequested()
{
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)
{
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
if (result == MessageBoxResult.Yes)
{
logger.Debug("The user confirmed reloading the current page...");
Control.Reload();
}
else
{
logger.Debug("The user aborted reloading the current page.");
}
}
else if (WindowSettings.AllowReloading)
{
logger.Debug("Reloading current page...");
Control.Reload();
}
else
{
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
}
}
private void ShowDownUploadNotAllowedMessage(bool isDownload = true)
{
var message = isDownload ? TextKey.MessageBox_DownloadNotAllowed : TextKey.MessageBox_UploadNotAllowed;
var title = isDownload ? TextKey.MessageBox_DownloadNotAllowedTitle : TextKey.MessageBox_UploadNotAllowedTitle;
messageBox.Show(message, title, icon: MessageBoxIcon.Warning, parent: window);
}
private void Window_AddressChanged(string address)
{
var isValid = Uri.TryCreate(address, UriKind.Absolute, out _) || Uri.TryCreate($"https://{address}", UriKind.Absolute, out _);
if (isValid)
{
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
Control.NavigateTo(address);
}
else
{
logger.Debug($"The user requested to navigate to '{address}', but the URI is not valid.");
window.UpdateAddress(string.Empty);
}
}
private void Window_BackwardNavigationRequested()
{
logger.Debug("Navigating backwards...");
Control.NavigateBackwards();
}
private void Window_Closing()
{
logger.Debug($"Window is closing...");
}
private void Window_Closed()
{
logger.Debug($"Window has been closed.");
Control.Destroy();
Closed?.Invoke(Id);
}
private void Window_DeveloperConsoleRequested()
{
logger.Debug("Showing developer console...");
Control.ShowDeveloperConsole();
}
private void Window_FindRequested(string term, bool isInitial, bool caseSensitive, bool forward = true)
{
if (settings.AllowFind)
{
findParameters.caseSensitive = caseSensitive;
findParameters.forward = forward;
findParameters.isInitial = isInitial;
findParameters.term = term;
Control.Find(term, isInitial, caseSensitive, forward);
}
}
private void Window_ForwardNavigationRequested()
{
logger.Debug("Navigating forwards...");
Control.NavigateForwards();
}
private void Window_LoseFocusRequested(bool forward)
{
LoseFocusRequested?.Invoke(forward);
}
private void ZoomInRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
{
zoomLevel += ZOOM_FACTOR;
Control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
}
}
private void ZoomOutRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
{
zoomLevel -= ZOOM_FACTOR;
Control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
}
}
private void ZoomResetRequested()
{
if (settings.AllowPageZoom)
{
zoomLevel = 0;
Control.Zoom(0);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Reset page zoom to {CalculateZoomPercentage()}%.");
}
}
private void AutoFind()
{
if (settings.AllowFind && !string.IsNullOrEmpty(findParameters.term) && !CLEAR_FIND_TERM.Equals(findParameters.term, StringComparison.OrdinalIgnoreCase))
{
Control.Find(findParameters.term, findParameters.isInitial, findParameters.caseSensitive, findParameters.forward);
}
}
private double CalculateZoomPercentage()
{
return (zoomLevel * 25.0) + 100.0;
}
}
}