diff --git a/SafeExamBrowser.Browser.Contracts/Events/TerminationRequestedEventHandler.cs b/SafeExamBrowser.Browser.Contracts/Events/TerminationRequestedEventHandler.cs
new file mode 100644
index 00000000..ba423a11
--- /dev/null
+++ b/SafeExamBrowser.Browser.Contracts/Events/TerminationRequestedEventHandler.cs
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2019 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/.
+ */
+
+namespace SafeExamBrowser.Browser.Contracts.Events
+{
+ ///
+ /// Event handler used to indicate that a termination request has been detected.
+ ///
+ public delegate void TerminationRequestedEventHandler();
+}
diff --git a/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs b/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs
index 8604a660..29ca0e1e 100644
--- a/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs
+++ b/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs
@@ -20,5 +20,10 @@ namespace SafeExamBrowser.Browser.Contracts
/// Event fired when the browser application detects a download request for an application configuration file.
///
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
+
+ ///
+ /// Event fired when the browser application detects a request to terminate SEB.
+ ///
+ event TerminationRequestedEventHandler TerminationRequested;
}
}
diff --git a/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj b/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj
index 1810bd3f..9702d106 100644
--- a/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj
+++ b/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj
@@ -57,6 +57,7 @@
+
diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs
index 0b18ecf9..91deaee4 100644
--- a/SafeExamBrowser.Browser/BrowserApplication.cs
+++ b/SafeExamBrowser.Browser/BrowserApplication.cs
@@ -20,7 +20,6 @@ using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
-using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts;
@@ -49,6 +48,7 @@ namespace SafeExamBrowser.Browser
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event WindowsChangedEventHandler WindowsChanged;
+ public event TerminationRequestedEventHandler TerminationRequested;
public BrowserApplication(
AppConfig appConfig,
@@ -127,6 +127,7 @@ namespace SafeExamBrowser.Browser
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;
instance.Terminated += Instance_Terminated;
+ instance.TerminationRequested += () => TerminationRequested?.Invoke();
instance.Initialize();
instances.Add(instance);
diff --git a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs
index b3dc3e78..f3856519 100644
--- a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs
+++ b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs
@@ -40,9 +40,9 @@ namespace SafeExamBrowser.Browser
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
+ private string startUrl;
private IText text;
private IUserInterfaceFactory uiFactory;
- private string url;
private double zoomLevel;
private WindowSettings WindowSettings
@@ -59,6 +59,7 @@ namespace SafeExamBrowser.Browser
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event PopupRequestedEventHandler PopupRequested;
internal event InstanceTerminatedEventHandler Terminated;
+ internal event TerminationRequestedEventHandler TerminationRequested;
public event IconChangedEventHandler IconChanged;
public event TitleChangedEventHandler TitleChanged;
@@ -72,7 +73,7 @@ namespace SafeExamBrowser.Browser
IModuleLogger logger,
IText text,
IUserInterfaceFactory uiFactory,
- string url)
+ string startUrl)
{
this.appConfig = appConfig;
this.Id = id;
@@ -83,7 +84,7 @@ namespace SafeExamBrowser.Browser
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
- this.url = url;
+ this.startUrl = startUrl;
}
public void Activate()
@@ -106,12 +107,12 @@ namespace SafeExamBrowser.Browser
{
var contextMenuHandler = new ContextMenuHandler();
var displayHandler = new DisplayHandler();
- var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} {Id}");
+ var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler();
var requestFilter = new RequestFilter();
- var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}");
+ var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, settings, text);
Icon = new BrowserIconResource();
@@ -124,11 +125,12 @@ namespace SafeExamBrowser.Browser
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
+ requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
InitializeRequestFilter(requestFilter);
- control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url);
+ control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, startUrl);
control.AddressChanged += Control_AddressChanged;
control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged;
@@ -276,6 +278,35 @@ namespace SafeExamBrowser.Browser
}
}
+ private void RequestHandler_QuitUrlVisited(string url)
+ {
+ Task.Run(() =>
+ {
+ 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 '{url}', forwarding request...");
+ TerminationRequested?.Invoke();
+ }
+ else
+ {
+ logger.Info($"User aborted termination via quit URL '{url}'.");
+ }
+ }
+ else
+ {
+ logger.Info($"Automatically requesting termination due to quit URL '{url}'...");
+ TerminationRequested?.Invoke();
+ }
+ });
+ }
+
private void RequestHandler_RequestBlocked(string url)
{
Task.Run(() =>
@@ -285,7 +316,7 @@ namespace SafeExamBrowser.Browser
control.TitleChanged -= Control_TitleChanged;
- if (url == this.url)
+ if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
{
window.UpdateTitle($"*** {title} ***");
TitleChanged?.Invoke($"*** {title} ***");
diff --git a/SafeExamBrowser.Browser/Events/RequestBlockedEventHandler.cs b/SafeExamBrowser.Browser/Events/UrlEventHandler.cs
similarity index 84%
rename from SafeExamBrowser.Browser/Events/RequestBlockedEventHandler.cs
rename to SafeExamBrowser.Browser/Events/UrlEventHandler.cs
index 0c28057a..cf3e3b8a 100644
--- a/SafeExamBrowser.Browser/Events/RequestBlockedEventHandler.cs
+++ b/SafeExamBrowser.Browser/Events/UrlEventHandler.cs
@@ -8,5 +8,5 @@
namespace SafeExamBrowser.Browser.Events
{
- internal delegate void RequestBlockedEventHandler(string url);
+ internal delegate void UrlEventHandler(string url);
}
diff --git a/SafeExamBrowser.Browser/Handlers/RequestHandler.cs b/SafeExamBrowser.Browser/Handlers/RequestHandler.cs
index cfbcddd8..b33f3aa5 100644
--- a/SafeExamBrowser.Browser/Handlers/RequestHandler.cs
+++ b/SafeExamBrowser.Browser/Handlers/RequestHandler.cs
@@ -25,7 +25,8 @@ namespace SafeExamBrowser.Browser.Handlers
private ResourceHandler resourceHandler;
private BrowserSettings settings;
- internal event RequestBlockedEventHandler RequestBlocked;
+ internal event UrlEventHandler QuitUrlVisited;
+ internal event UrlEventHandler RequestBlocked;
internal RequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, BrowserSettings settings, IText text)
{
@@ -60,6 +61,13 @@ namespace SafeExamBrowser.Browser.Handlers
protected override bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
{
+ if (IsQuitUrl(request))
+ {
+ QuitUrlVisited?.Invoke(request.Url);
+
+ return true;
+ }
+
if (Block(request))
{
RequestBlocked?.Invoke(request.Url);
@@ -70,6 +78,18 @@ namespace SafeExamBrowser.Browser.Handlers
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
}
+ private bool IsQuitUrl(IRequest request)
+ {
+ var isQuitUrl = settings.QuitUrl?.Equals(request.Url, StringComparison.OrdinalIgnoreCase) == true;
+
+ if (isQuitUrl)
+ {
+ logger.Debug($"Detected quit URL '{request.Url}'.");
+ }
+
+ return isQuitUrl;
+ }
+
private bool Block(IRequest request)
{
if (settings.Filter.ProcessMainRequests)
diff --git a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj
index 877dec92..8b4324f4 100644
--- a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj
+++ b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj
@@ -68,7 +68,7 @@
-
+
diff --git a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs
index a728f056..9c1e240b 100644
--- a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs
+++ b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs
@@ -232,6 +232,17 @@ namespace SafeExamBrowser.Client.UnitTests
It.Is(w => w == lockScreen.Object)), Times.Exactly(attempt - 1));
}
+ [TestMethod]
+ public void Browser_MustTerminateIfRequested()
+ {
+ runtimeProxy.Setup(p => p.RequestShutdown()).Returns(new CommunicationResult(true));
+
+ sut.TryStart();
+ browser.Raise(b => b.TerminationRequested += null);
+
+ runtimeProxy.Verify(p => p.RequestShutdown(), Times.Once);
+ }
+
[TestMethod]
public void Communication_MustCorrectlyHandleMessageBoxRequest()
{
diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs
index ef6eb1e9..7a174efb 100644
--- a/SafeExamBrowser.Client/ClientController.cs
+++ b/SafeExamBrowser.Client/ClientController.cs
@@ -171,6 +171,7 @@ namespace SafeExamBrowser.Client
applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted;
applicationMonitor.TerminationFailed += ApplicationMonitor_TerminationFailed;
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
+ Browser.TerminationRequested += Browser_TerminationRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
ClientHost.ReconfigurationDenied += ClientHost_ReconfigurationDenied;
@@ -321,8 +322,7 @@ namespace SafeExamBrowser.Client
}
else if (result.OptionId == terminateOption.Id)
{
- logger.Info("Initiating shutdown request...");
-
+ logger.Info("Attempting to shutdown as requested by the user...");
TryRequestShutdown();
}
}
@@ -343,6 +343,12 @@ namespace SafeExamBrowser.Client
}
}
+ private void Browser_TerminationRequested()
+ {
+ logger.Info("Attempting to shutdown as requested by the browser...");
+ TryRequestShutdown();
+ }
+
private void Browser_ConfigurationDownloadFinished(bool success, string filePath = null)
{
if (success)
diff --git a/SafeExamBrowser.Configuration.UnitTests/DataFormats/XmlParserTests.cs b/SafeExamBrowser.Configuration.UnitTests/DataFormats/XmlParserTests.cs
index d5d88d4f..76618163 100644
--- a/SafeExamBrowser.Configuration.UnitTests/DataFormats/XmlParserTests.cs
+++ b/SafeExamBrowser.Configuration.UnitTests/DataFormats/XmlParserTests.cs
@@ -94,8 +94,8 @@ namespace SafeExamBrowser.Configuration.UnitTests.DataFormats
Assert.AreEqual(true, result.RawData[Keys.Browser.AllowConfigurationDownloads]);
Assert.AreEqual(0, result.RawData[Keys.ConfigurationFile.ConfigurationPurpose]);
- Assert.AreEqual("https://safeexambrowser.org/start", result.RawData[Keys.General.StartUrl]);
- Assert.AreEqual(true, result.RawData[Keys.Input.Keyboard.EnableF5]);
+ Assert.AreEqual("https://safeexambrowser.org/start", result.RawData[Keys.Browser.StartUrl]);
+ Assert.AreEqual(true, result.RawData[Keys.Keyboard.EnableF5]);
Assert.IsInstanceOfType(result.RawData[Keys.Network.Certificates.EmbeddedCertificates], typeof(List