788 lines
26 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|