diff --git a/SafeExamBrowser.Browser.Contracts/Events/LoseFocusRequestedEventHandler.cs b/SafeExamBrowser.Browser.Contracts/Events/LoseFocusRequestedEventHandler.cs
new file mode 100644
index 00000000..56af93ac
--- /dev/null
+++ b/SafeExamBrowser.Browser.Contracts/Events/LoseFocusRequestedEventHandler.cs
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2022 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 the user wants to move the focus away from the item.
+ ///
+ public delegate void LoseFocusRequestedEventHandler(bool forward);
+}
diff --git a/SafeExamBrowser.Browser.Contracts/Events/TabPressedEventHandler.cs b/SafeExamBrowser.Browser.Contracts/Events/TabPressedEventHandler.cs
new file mode 100644
index 00000000..f9586a6c
--- /dev/null
+++ b/SafeExamBrowser.Browser.Contracts/Events/TabPressedEventHandler.cs
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2022 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 the user pressed the tab key to move the focus forward or backward.
+ ///
+ public delegate void TabPressedEventHandler(bool forward);
+}
diff --git a/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs b/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs
index d89f9b57..e1d9df7a 100644
--- a/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs
+++ b/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs
@@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using System;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Browser.Contracts.Events;
@@ -30,5 +31,16 @@ namespace SafeExamBrowser.Browser.Contracts
/// Event fired when the browser application detects a request to terminate SEB.
///
event TerminationRequestedEventHandler TerminationRequested;
+
+ ///
+ /// Event fired when the user tries to focus the taskbar.
+ ///
+ event LoseFocusRequestedEventHandler LoseFocusRequested;
+
+ ///
+ /// Transfers the focus to the browser window.
+ /// If true, the first focusable element in the browser window receives focus (passing forward of focus). Otherwise, the last element.
+ ///
+ void Focus(bool forward);
}
}
diff --git a/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj b/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj
index 542024a9..02460564 100644
--- a/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj
+++ b/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj
@@ -57,6 +57,8 @@
+
+
diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs
index c98f4924..9f2504dc 100644
--- a/SafeExamBrowser.Browser/BrowserApplication.cs
+++ b/SafeExamBrowser.Browser/BrowserApplication.cs
@@ -58,6 +58,7 @@ namespace SafeExamBrowser.Browser
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
+ public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event TerminationRequestedEventHandler TerminationRequested;
public event WindowsChangedEventHandler WindowsChanged;
@@ -195,6 +196,7 @@ namespace SafeExamBrowser.Browser
window.ResetRequested += Window_ResetRequested;
window.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i);
window.TerminationRequested += () => TerminationRequested?.Invoke();
+ window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
window.InitializeControl();
windows.Add(window);
@@ -457,5 +459,13 @@ namespace SafeExamBrowser.Browser
CreateNewWindow();
logger.Info("Successfully reset browser.");
}
+
+ public void Focus(bool forward)
+ {
+ windows.ForEach(window =>
+ {
+ window.Focus(forward);
+ });
+ }
}
}
diff --git a/SafeExamBrowser.Browser/BrowserControl.cs b/SafeExamBrowser.Browser/BrowserControl.cs
index e9635c6c..3b569d43 100644
--- a/SafeExamBrowser.Browser/BrowserControl.cs
+++ b/SafeExamBrowser.Browser/BrowserControl.cs
@@ -15,6 +15,7 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser;
+using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
namespace SafeExamBrowser.Browser
@@ -143,5 +144,19 @@ namespace SafeExamBrowser.Browser
{
control.BrowserCore.SetZoomLevel(level);
}
+
+ ///
+ /// Executes the given Javascript code in the browser.
+ ///
+ public async void ExecuteJavascript(string javascript, Action callback)
+ {
+ var result = await this.control.EvaluateScriptAsync(javascript);
+ callback(new JavascriptResult()
+ {
+ Message = result.Message,
+ Result = result.Result,
+ Success = result.Success
+ });
+ }
}
}
diff --git a/SafeExamBrowser.Browser/BrowserWindow.cs b/SafeExamBrowser.Browser/BrowserWindow.cs
index e008f31c..89a3823b 100644
--- a/SafeExamBrowser.Browser/BrowserWindow.cs
+++ b/SafeExamBrowser.Browser/BrowserWindow.cs
@@ -80,6 +80,7 @@ namespace SafeExamBrowser.Browser
internal event PopupRequestedEventHandler PopupRequested;
internal event ResetRequestedEventHandler ResetRequested;
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
+ internal event LoseFocusRequestedEventHandler LoseFocusRequested;
internal event TerminationRequestedEventHandler TerminationRequested;
public event IconChangedEventHandler IconChanged;
@@ -162,6 +163,8 @@ namespace SafeExamBrowser.Browser
keyboardHandler.ZoomInRequested += ZoomInRequested;
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
+ keyboardHandler.TabPressed += TabPressed;
+ keyboardHandler.FocusAddressBarRequested += FocusAddressBarRequested;
resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
@@ -189,7 +192,7 @@ namespace SafeExamBrowser.Browser
internal void InitializeWindow()
{
- window = uiFactory.CreateBrowserWindow(Control, settings, isMainWindow);
+ window = uiFactory.CreateBrowserWindow(Control, settings, isMainWindow, this.logger);
window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.Closed += Window_Closed;
@@ -198,6 +201,7 @@ namespace SafeExamBrowser.Browser
window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested;
+ window.LoseFocusRequested += Window_LoseFocusRequested;
window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested;
@@ -454,6 +458,11 @@ namespace SafeExamBrowser.Browser
}
}
+ private void FocusAddressBarRequested()
+ {
+ window.FocusAddressBar();
+ }
+
private ChromiumHostControl LifeSpanHandler_CreatePopup()
{
var args = new PopupRequestedEventArgs();
@@ -655,6 +664,11 @@ namespace SafeExamBrowser.Browser
Control.NavigateForwards();
}
+ private void Window_LoseFocusRequested(bool forward)
+ {
+ LoseFocusRequested?.Invoke(forward);
+ }
+
private void ZoomInRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
@@ -688,6 +702,43 @@ namespace SafeExamBrowser.Browser
}
}
+ private void TabPressed(bool shiftPressed)
+ {
+ this.Control.ExecuteJavascript("document.activeElement.tagName", result =>
+ {
+ var tagName = result.Result as string;
+ if (tagName != null)
+ {
+ if (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
+ {
+ this.LoseFocusRequested?.Invoke(true);
+ }
+ }
+ }
+ });
+ }
+
+ internal void Focus(bool forward)
+ {
+ if (forward)
+ {
+ window.FocusToolbar(forward);
+ }
+ else
+ {
+ window.FocusBrowser();
+
+ this.Activate();
+ }
+ }
+
private double CalculateZoomPercentage()
{
return (zoomLevel * 25.0) + 100.0;
diff --git a/SafeExamBrowser.Browser/Handlers/KeyboardHandler.cs b/SafeExamBrowser.Browser/Handlers/KeyboardHandler.cs
index ec54b60d..aae93188 100644
--- a/SafeExamBrowser.Browser/Handlers/KeyboardHandler.cs
+++ b/SafeExamBrowser.Browser/Handlers/KeyboardHandler.cs
@@ -8,6 +8,7 @@
using System.Windows.Forms;
using CefSharp;
+using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts;
namespace SafeExamBrowser.Browser.Handlers
@@ -20,6 +21,10 @@ namespace SafeExamBrowser.Browser.Handlers
internal event ActionRequestedEventHandler ZoomInRequested;
internal event ActionRequestedEventHandler ZoomOutRequested;
internal event ActionRequestedEventHandler ZoomResetRequested;
+ internal event ActionRequestedEventHandler FocusAddressBarRequested;
+ internal event TabPressedEventHandler TabPressed;
+
+ private int? currentKeyDown = null;
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
{
@@ -38,6 +43,11 @@ namespace SafeExamBrowser.Browser.Handlers
HomeNavigationRequested?.Invoke();
}
+ if (ctrl && keyCode == (int) Keys.L)
+ {
+ FocusAddressBarRequested?.Invoke();
+ }
+
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && keyCode == (int) Keys.Oemplus) || (ctrl && shift && keyCode == (int) Keys.D1))
{
ZoomInRequested?.Invoke();
@@ -52,8 +62,14 @@ namespace SafeExamBrowser.Browser.Handlers
{
ZoomResetRequested?.Invoke();
}
+
+ if (keyCode == (int)Keys.Tab && keyCode == currentKeyDown)
+ {
+ TabPressed?.Invoke(shift);
+ }
}
+ currentKeyDown = null;
return false;
}
@@ -66,6 +82,11 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
+ if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
+ {
+ currentKeyDown = keyCode;
+ }
+
return false;
}
}
diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs
index 1a59d1e9..d5573a5d 100644
--- a/SafeExamBrowser.Client/ClientController.cs
+++ b/SafeExamBrowser.Client/ClientController.cs
@@ -188,6 +188,7 @@ namespace SafeExamBrowser.Client
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected;
Browser.TerminationRequested += Browser_TerminationRequested;
+ Browser.LoseFocusRequested += Browser_LoseFocusRequested;
ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
@@ -198,6 +199,7 @@ namespace SafeExamBrowser.Client
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
runtime.ConnectionLost += Runtime_ConnectionLost;
systemMonitor.SessionSwitched += SystemMonitor_SessionSwitched;
+ taskbar.LoseFocusRequested += Taskbar_LoseFocusRequested;
taskbar.QuitButtonClicked += Shell_QuitButtonClicked;
foreach (var activator in context.Activators.OfType())
@@ -211,6 +213,16 @@ namespace SafeExamBrowser.Client
}
}
+ private void Taskbar_LoseFocusRequested(bool forward)
+ {
+ this.Browser.Focus(forward);
+ }
+
+ private void Browser_LoseFocusRequested(bool forward)
+ {
+ this.taskbar.Focus(forward);
+ }
+
private void DeregisterEvents()
{
actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked;
@@ -226,6 +238,7 @@ namespace SafeExamBrowser.Client
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
Browser.SessionIdentifierDetected -= Browser_SessionIdentifierDetected;
Browser.TerminationRequested -= Browser_TerminationRequested;
+ Browser.LoseFocusRequested -= Browser_LoseFocusRequested;
}
if (ClientHost != null)
diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs
index 5c9271d0..b2ce756d 100644
--- a/SafeExamBrowser.I18n.Contracts/TextKey.cs
+++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs
@@ -24,12 +24,19 @@ namespace SafeExamBrowser.I18n.Contracts
Browser_LoadErrorTitle,
Browser_Name,
Browser_Tooltip,
+ BrowserWindow_BackwardButton,
BrowserWindow_DeveloperConsoleMenuItem,
BrowserWindow_Downloading,
BrowserWindow_DownloadCancelled,
BrowserWindow_DownloadComplete,
+ BrowserWindow_DownloadsButton,
BrowserWindow_FindCaseSensitive,
BrowserWindow_FindMenuItem,
+ BrowserWindow_ForwardButton,
+ BrowserWindow_HomeButton,
+ BrowserWindow_MenuButton,
+ BrowserWindow_ReloadButton,
+ BrowserWindow_UrlTextBox,
BrowserWindow_ZoomMenuItem,
Build,
ExamSelectionDialog_Cancel,
diff --git a/SafeExamBrowser.I18n/Data/de.xml b/SafeExamBrowser.I18n/Data/de.xml
index fb6d4cbc..f9f905d8 100644
--- a/SafeExamBrowser.I18n/Data/de.xml
+++ b/SafeExamBrowser.I18n/Data/de.xml
@@ -48,6 +48,27 @@
Seite durchsuchen...
+
+ Neu laden
+
+
+ Rückwärts
+
+
+ Vorwärts
+
+
+ Download
+
+
+ Home
+
+
+ Menü
+
+
+ URL eingeben
+
Seiten-Zoom
diff --git a/SafeExamBrowser.I18n/Data/en.xml b/SafeExamBrowser.I18n/Data/en.xml
index ab2829f5..00fb5b46 100644
--- a/SafeExamBrowser.I18n/Data/en.xml
+++ b/SafeExamBrowser.I18n/Data/en.xml
@@ -48,6 +48,27 @@
Search page...
+
+ Reload
+
+
+ Backward
+
+
+ Forward
+
+
+ Download
+
+
+ Home
+
+
+ Menu
+
+
+ Enter URL
+
Page Zoom
diff --git a/SafeExamBrowser.I18n/Data/fr.xml b/SafeExamBrowser.I18n/Data/fr.xml
index 37bbf95b..f2f89c86 100644
--- a/SafeExamBrowser.I18n/Data/fr.xml
+++ b/SafeExamBrowser.I18n/Data/fr.xml
@@ -48,6 +48,27 @@
Rechercher dans la page...
+
+ Recharger
+
+
+ En arrière
+
+
+ Avant
+
+
+ Télécharger
+
+
+ Accueil
+
+
+ Menu
+
+
+ Entrer l'URL
+
Zoom de la page
diff --git a/SafeExamBrowser.I18n/Data/it.xml b/SafeExamBrowser.I18n/Data/it.xml
index 9fb9d6fd..b99ba6a1 100644
--- a/SafeExamBrowser.I18n/Data/it.xml
+++ b/SafeExamBrowser.I18n/Data/it.xml
@@ -48,6 +48,27 @@
Pagina di ricerca ...
+
+ Ricarica
+
+
+ Indietro
+
+
+ Avanti
+
+
+ Scaricare
+
+
+ Home
+
+
+ Menu
+
+
+ Inserisci URL
+
Zoom della pagina
diff --git a/SafeExamBrowser.I18n/Data/zh.xml b/SafeExamBrowser.I18n/Data/zh.xml
index 653d2e4c..87076aa3 100644
--- a/SafeExamBrowser.I18n/Data/zh.xml
+++ b/SafeExamBrowser.I18n/Data/zh.xml
@@ -45,6 +45,27 @@
下载完成
+
+ 重新加载
+
+
+ 向后
+
+
+ 前进
+
+
+ 下载
+
+
+ 首页
+
+
+ 菜单
+
+
+ 输入网址
+
页面缩放
diff --git a/SafeExamBrowser.UserInterface.Contracts/Browser/Data/JavascriptResult.cs b/SafeExamBrowser.UserInterface.Contracts/Browser/Data/JavascriptResult.cs
new file mode 100644
index 00000000..82459ff0
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Contracts/Browser/Data/JavascriptResult.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SafeExamBrowser.UserInterface.Contracts.Browser.Data
+{
+ ///
+ /// The data resulting from a Javascript expression evaluation.
+ ///
+ public class JavascriptResult
+ {
+ ///
+ /// Indicates if the Javascript was evaluated successfully or not.
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// The error message, in case of an unsuccessful evaluation of the Javascript expression.
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// The data item returned by the Javascript expression.
+ ///
+ public dynamic Result { get; set; }
+ }
+}
diff --git a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserControl.cs b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserControl.cs
index 3ec45476..a27c5c8e 100644
--- a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserControl.cs
+++ b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserControl.cs
@@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Browser
@@ -61,6 +62,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
///
void Destroy();
+ ///
+ /// Executes the given Javascript code in the browser.
+ ///
+ void ExecuteJavascript(string javascript, System.Action callback);
+
///
/// Attempts to find the given term on the current page according to the specified parameters.
///
diff --git a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs
index 970f2b02..b41cc25d 100644
--- a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs
+++ b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs
@@ -7,6 +7,7 @@
*/
using System;
+using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
@@ -64,6 +65,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
///
event ActionRequestedEventHandler HomeNavigationRequested;
+ ///
+ /// Event fired when the browser window wants to lose focus to the taskbar.
+ ///
+ event LoseFocusRequestedEventHandler LoseFocusRequested;
+
///
/// Event fired when the user would like to reload the current page.
///
@@ -123,5 +129,18 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// Updates the display value of the current page zoom. Value is expected to be in percentage.
///
void UpdateZoomLevel(double value);
+
+ ///
+ /// Sets the focus to the toolbar.
+ ///
+ /// If true, the first focusable control on the toolbar gets focused. If false, the last one.
+ void FocusToolbar(bool forward);
+
+ ///
+ /// Sets the focus to the browser.
+ ///
+ void FocusBrowser();
+
+ void FocusAddressBar();
}
}
diff --git a/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs
index 355c123c..d5447730 100644
--- a/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs
+++ b/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs
@@ -56,7 +56,7 @@ namespace SafeExamBrowser.UserInterface.Contracts
///
/// Creates a new browser window loaded with the given browser control and settings.
///
- IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow);
+ IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow, ILogger logger);
///
/// Creates an exam selection dialog for the given exams.
diff --git a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj
index 21563d27..5eb5ab83 100644
--- a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj
+++ b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj
@@ -55,6 +55,7 @@
+
@@ -110,6 +111,10 @@
{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}
SafeExamBrowser.Applications.Contracts
+
+ {5fb5273d-277c-41dd-8593-a25ce1aff2e9}
+ SafeExamBrowser.Browser.Contracts
+
{7d74555e-63e1-4c46-bd0a-8580552368c8}
SafeExamBrowser.Configuration.Contracts
diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs
index 76721115..0dd67e8a 100644
--- a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs
+++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs
@@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
@@ -31,6 +32,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
///
event QuitButtonClickedEventHandler QuitButtonClicked;
+ ///
+ /// Event fired when the Taskbar wants to lose focus.
+ ///
+ event LoseFocusRequestedEventHandler LoseFocusRequested;
+
///
/// Adds the given application control to the taskbar.
///
@@ -70,5 +76,10 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// Shows the taskbar.
///
void Show();
+
+ ///
+ /// Puts the focus on the taskbar.
+ ///
+ void Focus(bool forward = true);
}
}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutButton.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutButton.xaml
index abdcaff9..0e1f4445 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutButton.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutButton.xaml
@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
- mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250">
+ mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250" IsTabStop="True">
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml
index 4ccf82d2..d991f4c1 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml
@@ -16,7 +16,7 @@
-
+
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs
index 5a1a09f9..24eaedf8 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs
@@ -41,7 +41,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
InitializeLayouts();
keyboard.LayoutChanged += Keyboard_LayoutChanged;
- Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
+ Button.Click += (o, args) =>
+ {
+ Popup.IsOpen = !Popup.IsOpen;
+ this.Dispatcher.BeginInvoke((System.Action)(() =>
+ {
+ LayoutsStackPanel.Children[0].Focus();
+ }));
+ };
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
@@ -90,5 +97,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
Text.Text = layout.CultureName;
Button.ToolTip = tooltip;
}
+
+ private void Popup_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ if (e.Key == System.Windows.Input.Key.Enter || e.Key == System.Windows.Input.Key.Escape)
+ {
+ Popup.IsOpen = false;
+ Button.Focus();
+ }
+ }
}
}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml
index cb433bf0..229251fc 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml
@@ -16,7 +16,7 @@
-
+
@@ -24,7 +24,7 @@
-
@@ -33,7 +33,7 @@
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml.cs
index 25427637..541416ad 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/AudioControl.xaml.cs
@@ -60,6 +60,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
{
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
+ Volume.Focus();
};
Popup.Closed += (o, args) =>
@@ -167,5 +168,23 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
return IconResourceLoader.Load(resource);
}
+
+ private void Popup_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ if (e.Key == System.Windows.Input.Key.Escape)
+ {
+ Popup.IsOpen = false;
+ Button.Focus();
+ }
+ }
+
+ private void Volume_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ if (e.Key == System.Windows.Input.Key.Enter)
+ {
+ Popup.IsOpen = false;
+ Button.Focus();
+ }
+ }
}
}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/Clock.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/Clock.xaml
index 4d3ec857..93517d0f 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/Clock.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/Clock.xaml
@@ -11,7 +11,7 @@
-
-
+
+
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml
index 90170748..20a0a91d 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml
@@ -14,24 +14,22 @@
-
-
+
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml.cs
index 8dc22819..ad74c2fa 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutButton.xaml.cs
@@ -43,6 +43,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
CultureCodeTextBlock.Text = layout.CultureCode;
CultureNameTextBlock.Text = layout.CultureName;
LayoutNameTextBlock.Text = layout.LayoutName;
+ System.Windows.Automation.AutomationProperties.SetHelpText(Button, layout.LayoutName);
}
}
}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml
index 4f745fd4..ca2bcdf8 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml
@@ -16,7 +16,7 @@
-
+
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml.cs
index a607a677..da280d12 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/KeyboardLayoutControl.xaml.cs
@@ -45,7 +45,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
InitializeLayouts();
keyboard.LayoutChanged += Keyboard_LayoutChanged;
- Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
+ Button.Click += (o, args) =>
+ {
+ Popup.IsOpen = !Popup.IsOpen;
+ Task.Delay(200).ContinueWith(_ => this.Dispatcher.BeginInvoke((System.Action)(() =>
+ {
+ ((LayoutsStackPanel.Children[0] as ContentControl).Content as UIElement).Focus();
+ })));
+ };
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
@@ -114,5 +121,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
LayoutCultureCode.Text = layout.CultureCode;
Button.ToolTip = tooltip;
}
+
+ private void Popup_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ if (e.Key == System.Windows.Input.Key.Enter || e.Key == System.Windows.Input.Key.Escape)
+ {
+ Popup.IsOpen = false;
+ Button.Focus();
+ }
+ }
}
}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml
index 09c2e8a1..7522d2e6 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
- mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
+ mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40" Focusable="true" IsTabStop="True">
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs
index 7b593b59..227a287d 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs
@@ -8,6 +8,7 @@
using System;
using System.Windows;
+using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
@@ -101,6 +102,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
Button.ToolTip = tooltip;
PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed;
Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed;
+ AutomationProperties.SetHelpText(this, Button.ToolTip as string);
}
private void RenderCharge(double charge, BatteryChargeStatus status)
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/QuitButton.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/QuitButton.xaml
index 27a637d7..592ac0b4 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/QuitButton.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/QuitButton.xaml
@@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
+ x:Name="root"
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
@@ -15,6 +16,7 @@
+ Template="{StaticResource TaskbarButton}" VerticalAlignment="Stretch"
+ AutomationProperties.Name="{Binding Path=(AutomationProperties.Name), ElementName=root}"/>
diff --git a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj
index 47aa672f..e9d9e5ce 100644
--- a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj
+++ b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj
@@ -509,6 +509,14 @@
{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}
SafeExamBrowser.Applications.Contracts
+
+ {5fb5273d-277c-41dd-8593-a25ce1aff2e9}
+ SafeExamBrowser.Browser.Contracts
+
+
+ {04e653f1-98e6-4e34-9dd7-7f2bc1a8b767}
+ SafeExamBrowser.Browser
+
{7d74555e-63e1-4c46-bd0a-8580552368c8}
SafeExamBrowser.Configuration.Contracts
diff --git a/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs
index f84c0a5e..b715ea4f 100644
--- a/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs
@@ -80,9 +80,9 @@ namespace SafeExamBrowser.UserInterface.Desktop
}
}
- public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow)
+ public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow, ILogger logger)
{
- return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text));
+ return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text, logger));
}
public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable exams)
diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml
index da5ae188..cd4b266d 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml
@@ -62,15 +62,15 @@
-
+
-
+
-
+
@@ -80,7 +80,7 @@
-
+
@@ -90,7 +90,7 @@
-
+
diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs
index 99f2c2fe..2d4f4126 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs
@@ -8,14 +8,17 @@
using System;
using System.ComponentModel;
+using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
+using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
+using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser;
@@ -33,9 +36,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
private readonly bool isMainWindow;
private readonly BrowserSettings settings;
private readonly IText text;
+ private readonly ILogger logger;
+ private readonly IBrowserControl browserControl;
private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing;
+ private bool browserControlGetsFocusFromTaskbar = false;
+ private IInputElement tabKeyDownFocusElement = null;
private WindowSettings WindowSettings
{
@@ -56,6 +63,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
public event ActionRequestedEventHandler ZoomInRequested;
public event ActionRequestedEventHandler ZoomOutRequested;
public event ActionRequestedEventHandler ZoomResetRequested;
+ public event LoseFocusRequestedEventHandler LoseFocusRequested;
event WindowClosedEventHandler IWindow.Closed
{
@@ -69,11 +77,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
remove { closing -= value; }
}
- internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text)
+ internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger)
{
this.isMainWindow = isMainWindow;
this.settings = settings;
this.text = text;
+ this.logger = logger;
+ this.browserControl = browserControl;
InitializeComponent();
InitializeBrowserWindow(browserControl);
@@ -199,6 +209,33 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
}
}
+ private void BrowserWindow_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Tab)
+ {
+ var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
+ if (Toolbar.IsKeyboardFocusWithin && hasShift)
+ {
+ var firstActiveElementInToolbar = Toolbar.PredictFocus(FocusNavigationDirection.Right);
+ if (firstActiveElementInToolbar is System.Windows.UIElement)
+ {
+ var control = firstActiveElementInToolbar as System.Windows.UIElement;
+ if (control.IsKeyboardFocusWithin)
+ {
+ this.LoseFocusRequested?.Invoke(false);
+ e.Handled = true;
+ }
+ }
+ }
+
+ tabKeyDownFocusElement = FocusManager.GetFocusedElement(this);
+ }
+ else
+ {
+ tabKeyDownFocusElement = null;
+ }
+ }
+
private void BrowserWindow_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.F5)
@@ -215,6 +252,65 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
ShowFindbar();
}
+
+ if (e.Key == Key.Tab)
+ {
+ var hasCtrl = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
+ var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
+ if (BrowserControlHost.IsFocused && hasCtrl)
+ {
+ if (Findbar.Visibility == Visibility.Hidden || hasShift)
+ {
+ Toolbar.Focus();
+ }
+ else if (Toolbar.Visibility == Visibility.Hidden)
+ {
+ Findbar.Focus();
+ }
+ }
+ else if (MenuPopup.IsKeyboardFocusWithin)
+ {
+ var focusedElement = FocusManager.GetFocusedElement(this);
+ var focusedControl = focusedElement as System.Windows.Controls.Control;
+ var prevFocusedControl = tabKeyDownFocusElement as System.Windows.Controls.Control;
+ if (focusedControl != null && prevFocusedControl != null)
+ {
+ //var commonAncestor = focusedControl.FindCommonVisualAncestor(prevFocusedControl);
+ //var nextTab = GetNextTab(MenuPopup, this, true);
+ if (!hasShift && focusedControl.TabIndex < prevFocusedControl.TabIndex)
+ {
+ MenuPopup.IsOpen = false;
+ FocusBrowser();
+ }
+ else if (hasShift && focusedControl.TabIndex > prevFocusedControl.TabIndex)
+ {
+ MenuPopup.IsOpen = false;
+ MenuButton.Focus();
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get next tab order element. Copied from https://stackoverflow.com/questions/5756448/in-wpf-how-can-i-get-the-next-control-in-the-tab-order
+ ///
+ /// The element to get next tab order
+ /// The container element owning 'e'. Make sure this is a container of 'e'.
+ /// True if search only itself and inside of 'container'; otherwise false.
+ /// If true and next tab order element is outside of 'container', result in null.
+ /// Next tab order element or null if not found
+ public DependencyObject GetNextTab(DependencyObject e, DependencyObject container, bool goDownOnly)
+ {
+ var navigation = typeof(FrameworkElement)
+ .GetProperty("KeyboardNavigation", BindingFlags.NonPublic | BindingFlags.Static)
+ .GetValue(null);
+
+ var method = navigation
+ .GetType()
+ .GetMethod("GetNextTab", BindingFlags.NonPublic | BindingFlags.Instance);
+
+ return method.Invoke(navigation, new object[] { e, container, goDownOnly }) as DependencyObject;
}
private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
@@ -320,10 +416,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke();
HomeButton.Click += (o, args) => HomeNavigationRequested?.Invoke();
Loaded += BrowserWindow_Loaded;
- MenuButton.Click += (o, args) => MenuPopup.IsOpen = !MenuPopup.IsOpen;
+ MenuButton.Click += MenuButton_Click;
MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
+ KeyDown += BrowserWindow_KeyDown;
KeyUp += BrowserWindow_KeyUp;
LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
ReloadButton.Click += (o, args) => ReloadRequested?.Invoke();
@@ -338,6 +435,48 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke();
ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke();
ZoomResetButton.Click += (o, args) => ZoomResetRequested?.Invoke();
+ BrowserControlHost.GotKeyboardFocus += BrowserControlHost_GotKeyboardFocus;
+ }
+
+ private void MenuButton_Click(object sender, RoutedEventArgs e)
+ {
+ MenuPopup.IsOpen = !MenuPopup.IsOpen;
+ ZoomInButton.Focus();
+ }
+
+ private void BrowserControlHost_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
+ {
+ var forward = !this.browserControlGetsFocusFromTaskbar;
+
+ // focus the first / last element on the page
+ var javascript = @"
+if (typeof __SEB_focusElement === 'undefined') {
+ __SEB_focusElement = function (forward) {
+ var items = [].map
+ .call(document.body.querySelectorAll(['input', 'select', 'a[href]', 'textarea', 'button', '[tabindex]']), function(el, i) { return { el, i } })
+ .filter(function(e) { return e.el.tabIndex >= 0 && !e.el.disabled && e.el.offsetParent; })
+ .sort(function(a,b) { return a.el.tabIndex === b.el.tabIndex ? a.i - b.i : (a.el.tabIndex || 9E9) - (b.el.tabIndex || 9E9); })
+ var item = items[forward ? 1 : items.length - 1];
+ if (item && item.focus && typeof item.focus !== 'function')
+ throw ('item.focus is not a function, ' + typeof item.focus)
+ setTimeout(function () { item && item.focus && item.focus(); }, 20);
+ }
+}";
+ this.browserControl.ExecuteJavascript(javascript, result =>
+ {
+ if (!result.Success)
+ {
+ logger.Error($"Javascript error {result.Message}!");
+ }
+ });
+
+ this.browserControl.ExecuteJavascript("__SEB_focusElement(" + forward.ToString().ToLower() + ")", result =>
+ {
+ if (!result.Success)
+ {
+ logger.Error($"Javascript error {result.Message}!");
+ }
+ });
}
private void ApplySettings()
@@ -443,6 +582,58 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
FindCaseSensitiveCheckBox.Content = text.Get(TextKey.BrowserWindow_FindCaseSensitive);
FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem);
ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem);
+ ReloadButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ReloadButton));
+ BackwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_BackwardButton));
+ ForwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ForwardButton));
+ DownloadsButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_DownloadsButton));
+ HomeButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_HomeButton));
+ MenuButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_MenuButton));
+ UrlTextBox.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_UrlTextBox));
+ }
+
+ public void FocusToolbar(bool forward)
+ {
+ this.Dispatcher.BeginInvoke((Action)(async () =>
+ {
+ this.Activate();
+ await Task.Delay(50);
+
+ // focus all elements in the toolbar, such that the last element that is enabled gets focus
+ var buttons = new System.Windows.Controls.Control[] { ForwardButton, BackwardButton, ReloadButton, UrlTextBox, MenuButton, };
+ for (var i = forward ? 0 : buttons.Length - 1; i >= 0 && i < buttons.Length; i += forward ? 1 : -1)
+ {
+ if (buttons[i].IsEnabled && buttons[i].Visibility == Visibility.Visible)
+ {
+ buttons[i].Focus();
+ break;
+ }
+ }
+ }));
+ }
+
+ public void FocusBrowser()
+ {
+ this.Dispatcher.BeginInvoke((Action)(async () =>
+ {
+ this.FocusToolbar(false);
+ await Task.Delay(100);
+
+ this.browserControlGetsFocusFromTaskbar = true;
+
+ var focusedElement = FocusManager.GetFocusedElement(this) as UIElement;
+ focusedElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
+
+ await Task.Delay(150);
+ this.browserControlGetsFocusFromTaskbar = false;
+ }));
+ }
+
+ public void FocusAddressBar()
+ {
+ this.Dispatcher.BeginInvoke((Action)(async () =>
+ {
+ this.UrlTextBox.Focus();
+ }));
}
}
}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml b/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml
index 80199e63..2089f0d7 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml
+++ b/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml
@@ -6,6 +6,7 @@
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d" Title="Taskbar" Background="{DynamicResource BackgroundBrush}" Height="40" Width="750" WindowStyle="None" Topmost="True"
+ KeyDown="Window_KeyDown" KeyUp="Window_KeyUp"
ResizeMode="NoResize" Icon="../Images/SafeExamBrowser.ico">
diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs
index eca46944..f4a184d2 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs
@@ -6,8 +6,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using System;
using System.ComponentModel;
using System.Windows;
+using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
@@ -20,6 +22,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
private bool allowClose;
private ILogger logger;
+ private bool isQuitButtonFocusedAtKeyDown;
+ private bool isFirstChildFocusedAtKeyDown;
public bool ShowClock
{
@@ -31,6 +35,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
}
+ public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event QuitButtonClickedEventHandler QuitButtonClicked;
internal Taskbar(ILogger logger)
@@ -120,7 +125,9 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
Dispatcher.Invoke(() =>
{
- QuitButton.ToolTip = text.Get(TextKey.Shell_QuitButton);
+ var txt = text.Get(TextKey.Shell_QuitButton);
+ QuitButton.ToolTip = txt;
+ QuitButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, txt);
});
}
@@ -159,5 +166,82 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
Loaded += (o, args) => InitializeBounds();
QuitButton.Clicked += QuitButton_Clicked;
}
+
+ private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ this.isQuitButtonFocusedAtKeyDown = this.QuitButton.IsKeyboardFocusWithin;
+ this.isFirstChildFocusedAtKeyDown = this.ApplicationStackPanel.Children[0].IsKeyboardFocusWithin;
+ }
+
+ private void Window_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ if (e.Key == System.Windows.Input.Key.Tab)
+ {
+ var shift = System.Windows.Input.Keyboard.IsKeyDown(System.Windows.Input.Key.LeftShift);
+ if (!shift && this.ApplicationStackPanel.Children[0].IsKeyboardFocusWithin && this.isQuitButtonFocusedAtKeyDown)
+ {
+ this.LoseFocusRequested?.Invoke(true);
+ e.Handled = true;
+ }
+ else if (shift && this.QuitButton.IsKeyboardFocusWithin && this.isFirstChildFocusedAtKeyDown)
+ {
+ this.LoseFocusRequested?.Invoke(false);
+ e.Handled = true;
+ }
+ }
+
+ this.isQuitButtonFocusedAtKeyDown = false;
+ this.isFirstChildFocusedAtKeyDown = false;
+ }
+
+ void ITaskbar.Focus(bool forward)
+ {
+ this.Dispatcher.BeginInvoke((Action)(() =>
+ {
+ base.Activate();
+ if (forward)
+ {
+ this.SetFocusWithin(this.ApplicationStackPanel.Children[0]);
+ }
+ else
+ {
+ this.QuitButton.Focus();
+ }
+ }));
+ }
+
+ private bool SetFocusWithin(UIElement uIElement)
+ {
+ if (uIElement.Focusable)
+ {
+ uIElement.Focus();
+ return true;
+ }
+
+ if (uIElement is System.Windows.Controls.Panel)
+ {
+ var panel = uIElement as System.Windows.Controls.Panel;
+ for (var i = 0; i < panel.Children.Count; i++)
+ {
+ if (this.SetFocusWithin(panel.Children[i]))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ else if (uIElement is System.Windows.Controls.ContentControl)
+ {
+ var control = uIElement as System.Windows.Controls.ContentControl;
+ var content = control.Content as UIElement;
+ if (content != null)
+ {
+ return this.SetFocusWithin(content);
+ }
+ }
+
+ return false;
+ }
}
}
diff --git a/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj b/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj
index 2a43e1aa..104239ef 100644
--- a/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj
+++ b/SafeExamBrowser.UserInterface.Mobile/SafeExamBrowser.UserInterface.Mobile.csproj
@@ -207,6 +207,10 @@
{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}
SafeExamBrowser.Applications.Contracts
+
+ {5fb5273d-277c-41dd-8593-a25ce1aff2e9}
+ SafeExamBrowser.Browser.Contracts
+
{7d74555e-63e1-4c46-bd0a-8580552368c8}
SafeExamBrowser.Configuration.Contracts
diff --git a/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs
index 3625a500..35b851af 100644
--- a/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/UserInterfaceFactory.cs
@@ -80,9 +80,9 @@ namespace SafeExamBrowser.UserInterface.Mobile
}
}
- public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow)
+ public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow, ILogger logger)
{
- return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text));
+ return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text, logger));
}
public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable exams)
diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs
index f2ffe1b4..8110d4d9 100644
--- a/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs
@@ -14,8 +14,10 @@ using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
+using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
+using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser;
@@ -36,6 +38,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing;
+ private ILogger logger;
private WindowSettings WindowSettings
{
@@ -51,6 +54,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
public event ActionRequestedEventHandler DeveloperConsoleRequested;
public event FindRequestedEventHandler FindRequested;
public event ActionRequestedEventHandler ForwardNavigationRequested;
+ public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event ActionRequestedEventHandler HomeNavigationRequested;
public event ActionRequestedEventHandler ReloadRequested;
public event ActionRequestedEventHandler ZoomInRequested;
@@ -69,11 +73,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
remove { closing -= value; }
}
- internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text)
+ internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger)
{
this.isMainWindow = isMainWindow;
this.settings = settings;
this.text = text;
+ this.logger = logger;
InitializeComponent();
InitializeBrowserWindow(browserControl);
@@ -454,5 +459,25 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem);
ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem);
}
+
+ public void FocusToolbar(bool forward)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void FocusBrowser()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Debug()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void FocusAddressBar()
+ {
+ this.UrlTextBox.Focus();
+ }
}
}
diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
index c47469d8..9ddfee07 100644
--- a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
@@ -8,6 +8,7 @@
using System.ComponentModel;
using System.Windows;
+using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
@@ -31,6 +32,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
}
+ public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event QuitButtonClickedEventHandler QuitButtonClicked;
internal Taskbar(ILogger logger)
@@ -159,5 +161,18 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
Loaded += (o, args) => InitializeBounds();
QuitButton.Clicked += QuitButton_Clicked;
}
+
+ void ITaskbar.Focus(bool fromTop)
+ {
+ base.Activate();
+ if (fromTop)
+ {
+ this.ApplicationStackPanel.Children[0].Focus();
+ }
+ else
+ {
+ this.QuitButton.Focus();
+ }
+ }
}
}