626 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			626 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
 | 
						|
 * 
 | 
						|
 * 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.Net.Http;
 | 
						|
using System.Threading.Tasks;
 | 
						|
using CefSharp;
 | 
						|
using SafeExamBrowser.Applications.Contracts;
 | 
						|
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.Configuration.Contracts;
 | 
						|
using SafeExamBrowser.Configuration.Contracts.Cryptography;
 | 
						|
using SafeExamBrowser.Core.Contracts.Resources.Icons;
 | 
						|
using SafeExamBrowser.I18n.Contracts;
 | 
						|
using SafeExamBrowser.Logging.Contracts;
 | 
						|
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 Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
 | 
						|
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
 | 
						|
using TitleChangedEventHandler = SafeExamBrowser.Applications.Contracts.Events.TitleChangedEventHandler;
 | 
						|
 | 
						|
namespace SafeExamBrowser.Browser
 | 
						|
{
 | 
						|
	internal class BrowserApplicationInstance : IApplicationWindow
 | 
						|
	{
 | 
						|
		private const double ZOOM_FACTOR = 0.2;
 | 
						|
 | 
						|
		private AppConfig appConfig;
 | 
						|
		private IBrowserControl control;
 | 
						|
		private IBrowserWindow window;
 | 
						|
		private HttpClient httpClient;
 | 
						|
		private bool isMainInstance;
 | 
						|
		private IFileSystemDialog fileSystemDialog;
 | 
						|
		private IHashAlgorithm hashAlgorithm;
 | 
						|
		private IMessageBox messageBox;
 | 
						|
		private IModuleLogger logger;
 | 
						|
		private BrowserSettings settings;
 | 
						|
		private string startUrl;
 | 
						|
		private IText text;
 | 
						|
		private IUserInterfaceFactory uiFactory;
 | 
						|
		private double zoomLevel;
 | 
						|
 | 
						|
		private WindowSettings WindowSettings
 | 
						|
		{
 | 
						|
			get { return isMainInstance ? settings.MainWindow : settings.AdditionalWindow; }
 | 
						|
		}
 | 
						|
 | 
						|
		internal int Id { get; }
 | 
						|
 | 
						|
		public IntPtr Handle { get; private set; }
 | 
						|
		public IconResource Icon { get; private set; }
 | 
						|
		public string Title { get; private set; }
 | 
						|
 | 
						|
		internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
 | 
						|
		internal event PopupRequestedEventHandler PopupRequested;
 | 
						|
		internal event ResetRequestedEventHandler ResetRequested;
 | 
						|
		internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
 | 
						|
		internal event InstanceTerminatedEventHandler Terminated;
 | 
						|
		internal event TerminationRequestedEventHandler TerminationRequested;
 | 
						|
 | 
						|
		public event IconChangedEventHandler IconChanged;
 | 
						|
		public event TitleChangedEventHandler TitleChanged;
 | 
						|
 | 
						|
		public BrowserApplicationInstance(
 | 
						|
			AppConfig appConfig,
 | 
						|
			BrowserSettings settings,
 | 
						|
			int id,
 | 
						|
			bool isMainInstance,
 | 
						|
			IFileSystemDialog fileSystemDialog,
 | 
						|
			IHashAlgorithm hashAlgorithm,
 | 
						|
			IMessageBox messageBox,
 | 
						|
			IModuleLogger logger,
 | 
						|
			IText text,
 | 
						|
			IUserInterfaceFactory uiFactory,
 | 
						|
			string startUrl)
 | 
						|
		{
 | 
						|
			this.appConfig = appConfig;
 | 
						|
			this.Id = id;
 | 
						|
			this.httpClient = new HttpClient();
 | 
						|
			this.isMainInstance = isMainInstance;
 | 
						|
			this.fileSystemDialog = fileSystemDialog;
 | 
						|
			this.hashAlgorithm = hashAlgorithm;
 | 
						|
			this.messageBox = messageBox;
 | 
						|
			this.logger = logger;
 | 
						|
			this.settings = settings;
 | 
						|
			this.text = text;
 | 
						|
			this.uiFactory = uiFactory;
 | 
						|
			this.startUrl = startUrl;
 | 
						|
		}
 | 
						|
 | 
						|
		public void Activate()
 | 
						|
		{
 | 
						|
			window.BringToForeground();
 | 
						|
		}
 | 
						|
 | 
						|
		internal void Initialize()
 | 
						|
		{
 | 
						|
			InitializeControl();
 | 
						|
			InitializeWindow();
 | 
						|
		}
 | 
						|
 | 
						|
		internal void Terminate()
 | 
						|
		{
 | 
						|
			window.Close();
 | 
						|
			control.Destroy();
 | 
						|
		}
 | 
						|
 | 
						|
		private void InitializeControl()
 | 
						|
		{
 | 
						|
			var contextMenuHandler = new ContextMenuHandler();
 | 
						|
			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 lifeSpanHandler = new LifeSpanHandler();
 | 
						|
			var requestFilter = new RequestFilter();
 | 
						|
			var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
 | 
						|
			var resourceHandler = new ResourceHandler(appConfig, requestFilter, logger, settings, WindowSettings, text);
 | 
						|
			var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings, text);
 | 
						|
 | 
						|
			Icon = new BrowserIconResource();
 | 
						|
 | 
						|
			dialogHandler.DialogRequested += DialogHandler_DialogRequested;
 | 
						|
			displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
 | 
						|
			displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
 | 
						|
			downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
 | 
						|
			downloadHandler.DownloadUpdated += DownloadHandler_DownloadUpdated;
 | 
						|
			keyboardHandler.FindRequested += KeyboardHandler_FindRequested;
 | 
						|
			keyboardHandler.HomeNavigationRequested += HomeNavigationRequested;
 | 
						|
			keyboardHandler.ReloadRequested += ReloadRequested;
 | 
						|
			keyboardHandler.ZoomInRequested += ZoomInRequested;
 | 
						|
			keyboardHandler.ZoomOutRequested += ZoomOutRequested;
 | 
						|
			keyboardHandler.ZoomResetRequested += ZoomResetRequested;
 | 
						|
			lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
 | 
						|
			resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
 | 
						|
			requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
 | 
						|
			requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
 | 
						|
 | 
						|
			InitializeRequestFilter(requestFilter);
 | 
						|
 | 
						|
			control = new BrowserControl(
 | 
						|
				contextMenuHandler,
 | 
						|
				dialogHandler,
 | 
						|
				displayHandler,
 | 
						|
				downloadHandler,
 | 
						|
				keyboardHandler,
 | 
						|
				lifeSpanHandler,
 | 
						|
				requestHandler,
 | 
						|
				startUrl);
 | 
						|
			control.AddressChanged += Control_AddressChanged;
 | 
						|
			control.LoadFailed += Control_LoadFailed;
 | 
						|
			control.LoadingStateChanged += Control_LoadingStateChanged;
 | 
						|
			control.TitleChanged += Control_TitleChanged;
 | 
						|
 | 
						|
			control.Initialize();
 | 
						|
			logger.Debug("Initialized browser control.");
 | 
						|
		}
 | 
						|
 | 
						|
		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 InitializeWindow()
 | 
						|
		{
 | 
						|
			window = uiFactory.CreateBrowserWindow(control, settings, isMainInstance);
 | 
						|
			window.Closing += Window_Closing;
 | 
						|
			window.AddressChanged += Window_AddressChanged;
 | 
						|
			window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
 | 
						|
			window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
 | 
						|
			window.FindRequested += Window_FindRequested;
 | 
						|
			window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
 | 
						|
			window.HomeNavigationRequested += HomeNavigationRequested;
 | 
						|
			window.ReloadRequested += ReloadRequested;
 | 
						|
			window.ZoomInRequested += ZoomInRequested;
 | 
						|
			window.ZoomOutRequested += ZoomOutRequested;
 | 
						|
			window.ZoomResetRequested += ZoomResetRequested;
 | 
						|
			window.UpdateZoomLevel(CalculateZoomPercentage());
 | 
						|
			window.Show();
 | 
						|
 | 
						|
			Handle = window.Handle;
 | 
						|
 | 
						|
			logger.Debug("Initialized browser window.");
 | 
						|
		}
 | 
						|
 | 
						|
		private void Control_AddressChanged(string address)
 | 
						|
		{
 | 
						|
			logger.Debug($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
 | 
						|
			window.UpdateAddress(address);
 | 
						|
 | 
						|
			if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
 | 
						|
			{
 | 
						|
				Title = address;
 | 
						|
				window.UpdateTitle(address);
 | 
						|
				TitleChanged?.Invoke(address);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		private void Control_LoadFailed(int errorCode, string errorText, string url)
 | 
						|
		{
 | 
						|
			if (errorCode == (int) CefErrorCode.None)
 | 
						|
			{
 | 
						|
				logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
 | 
						|
			}
 | 
						|
			else if (errorCode == (int) CefErrorCode.Aborted)
 | 
						|
			{
 | 
						|
				logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
 | 
						|
			}
 | 
						|
			else if (errorCode == (int) CefErrorCode.UnknownUrlScheme)
 | 
						|
			{
 | 
						|
				logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} contains unknown URL scheme and will be handled by the OS.");
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				var title = text.Get(TextKey.Browser_LoadErrorTitle);
 | 
						|
				var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {errorText} ({errorCode})";
 | 
						|
 | 
						|
				logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {errorText} ({errorCode}).");
 | 
						|
 | 
						|
				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
 | 
						|
			{
 | 
						|
				initialPath = string.IsNullOrEmpty(settings.DownAndUploadDirectory) ? KnownFolders.Downloads.ExpandedPath : Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
 | 
						|
			}
 | 
						|
 | 
						|
			if (isAllowed)
 | 
						|
			{
 | 
						|
				var result = fileSystemDialog.Show(
 | 
						|
					args.Element,
 | 
						|
					args.Operation,
 | 
						|
					initialPath,
 | 
						|
					title: args.Title,
 | 
						|
					parent: window,
 | 
						|
					restrictNavigation: !settings.AllowCustomDownAndUploadLocation);
 | 
						|
 | 
						|
				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.");
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		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_DownloadUpdated(DownloadItemState state)
 | 
						|
		{
 | 
						|
			window.UpdateDownloadState(state);
 | 
						|
		}
 | 
						|
 | 
						|
		private void HomeNavigationRequested()
 | 
						|
		{
 | 
						|
			if (isMainInstance && (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 LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
 | 
						|
		{
 | 
						|
			var validCurrentUri = Uri.TryCreate(control.Address, UriKind.Absolute, out var currentUri);
 | 
						|
			var validNewUri = Uri.TryCreate(args.Url, 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 '{args.Url}'" : "")}...");
 | 
						|
					PopupRequested?.Invoke(args);
 | 
						|
					break;
 | 
						|
				case PopupPolicy.AllowSameWindow:
 | 
						|
				case PopupPolicy.AllowSameHostAndWindow when sameHost:
 | 
						|
					logger.Info($"Discarding request to open new window and loading{(WindowSettings.UrlPolicy.CanLog() ? $" '{args.Url}'" : "")} directly...");
 | 
						|
					control.NavigateTo(args.Url);
 | 
						|
					break;
 | 
						|
				case PopupPolicy.AllowSameHost when !sameHost:
 | 
						|
				case PopupPolicy.AllowSameHostAndWindow when !sameHost:
 | 
						|
					logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")} as it targets a different host.");
 | 
						|
					break;
 | 
						|
				default:
 | 
						|
					logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}.");
 | 
						|
					break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		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 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.Info($"Instance has terminated.");
 | 
						|
			control.Destroy();
 | 
						|
			Terminated?.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)
 | 
						|
			{
 | 
						|
				control.Find(term, isInitial, caseSensitive, forward);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		private void Window_ForwardNavigationRequested()
 | 
						|
		{
 | 
						|
			logger.Debug("Navigating forwards...");
 | 
						|
			control.NavigateForwards();
 | 
						|
		}
 | 
						|
 | 
						|
		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 double CalculateZoomPercentage()
 | 
						|
		{
 | 
						|
			return (zoomLevel * 25.0) + 100.0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |