Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof-of-concept desktop keyboard navigation #278

Merged
merged 20 commits into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Event handler used to indicate that the user wants to move the focus away from the item.
/// </summary>
public delegate void LoseFocusRequestedEventHandler(bool forward);
}
15 changes: 15 additions & 0 deletions SafeExamBrowser.Browser.Contracts/Events/TabPressedEventHandler.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Event handler used to indicate that the user pressed the tab key to move the focus forward or backward.
/// </summary>
public delegate void TabPressedEventHandler(bool forward);
}
12 changes: 12 additions & 0 deletions SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -30,5 +31,16 @@ public interface IBrowserApplication : IApplication
/// Event fired when the browser application detects a request to terminate SEB.
/// </summary>
event TerminationRequestedEventHandler TerminationRequested;

/// <summary>
/// Event fired when the user tries to focus the taskbar.
/// </summary>
event LoseFocusRequestedEventHandler LoseFocusRequested;

/// <summary>
/// Transfers the focus to the browser window.
/// <paramref name="forward">If true, the first focusable element in the browser window receives focus (passing forward of focus). Otherwise, the last element.</paramref>
/// </summary>
void Focus(bool forward);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
<Compile Include="Events\DownloadEventArgs.cs" />
<Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.cs" />
<Compile Include="Events\TabPressedEventHandler.cs" />
<Compile Include="Events\LoseFocusRequestedEventHandler.cs" />
<Compile Include="Events\SessionIdentifierDetectedEventHandler.cs" />
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
<Compile Include="Filters\IRequestFilter.cs" />
Expand Down
10 changes: 10 additions & 0 deletions SafeExamBrowser.Browser/BrowserApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class BrowserApplication : IBrowserApplication

public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event TerminationRequestedEventHandler TerminationRequested;
public event WindowsChangedEventHandler WindowsChanged;

Expand Down Expand Up @@ -195,6 +196,7 @@ private void CreateNewWindow(PopupRequestedEventArgs args = default)
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);
Expand Down Expand Up @@ -457,5 +459,13 @@ private void Window_ResetRequested()
CreateNewWindow();
logger.Info("Successfully reset browser.");
}

public void Focus(bool forward)
{
windows.ForEach(window =>
{
window.Focus(forward);
});
}
}
}
15 changes: 15 additions & 0 deletions SafeExamBrowser.Browser/BrowserControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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
Expand Down Expand Up @@ -143,5 +144,19 @@ public void Zoom(double level)
{
control.BrowserCore.SetZoomLevel(level);
}

/// <summary>
/// Executes the given Javascript code in the browser.
/// </summary>
public async void ExecuteJavascript(string javascript, Action<JavascriptResult> callback)
{
var result = await this.control.EvaluateScriptAsync(javascript);
callback(new JavascriptResult()
{
Message = result.Message,
Result = result.Result,
Success = result.Success
});
}
}
}
53 changes: 52 additions & 1 deletion SafeExamBrowser.Browser/BrowserWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private WindowSettings WindowSettings
internal event PopupRequestedEventHandler PopupRequested;
internal event ResetRequestedEventHandler ResetRequested;
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
internal event TerminationRequestedEventHandler TerminationRequested;

public event IconChangedEventHandler IconChanged;
Expand Down Expand Up @@ -162,6 +163,8 @@ internal void InitializeControl()
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;
Expand Down Expand Up @@ -189,7 +192,7 @@ internal void InitializeControl()

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;
Expand All @@ -198,6 +201,7 @@ internal void InitializeWindow()
window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested;
window.LoseFocusRequested += Window_LoseFocusRequested;
window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested;
Expand Down Expand Up @@ -454,6 +458,11 @@ private void KeyboardHandler_FindRequested()
}
}

private void FocusAddressBarRequested()
{
window.FocusAddressBar();
}

private ChromiumHostControl LifeSpanHandler_CreatePopup()
{
var args = new PopupRequestedEventArgs();
Expand Down Expand Up @@ -655,6 +664,11 @@ private void Window_ForwardNavigationRequested()
Control.NavigateForwards();
}

private void Window_LoseFocusRequested(bool forward)
{
LoseFocusRequested?.Invoke(forward);
}

private void ZoomInRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
Expand Down Expand Up @@ -688,6 +702,43 @@ private void ZoomResetRequested()
}
}

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;
Expand Down
21 changes: 21 additions & 0 deletions SafeExamBrowser.Browser/Handlers/KeyboardHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System.Windows.Forms;
using CefSharp;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts;

namespace SafeExamBrowser.Browser.Handlers
Expand All @@ -20,6 +21,10 @@ internal class KeyboardHandler : IKeyboardHandler
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)
{
Expand All @@ -38,6 +43,11 @@ public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType typ
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();
Expand All @@ -52,8 +62,14 @@ public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType typ
{
ZoomResetRequested?.Invoke();
}

if (keyCode == (int)Keys.Tab && keyCode == currentKeyDown)
{
TabPressed?.Invoke(shift);
}
}

currentKeyDown = null;
return false;
}

Expand All @@ -66,6 +82,11 @@ public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType
return true;
}

if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
{
currentKeyDown = keyCode;
}

return false;
}
}
Expand Down
13 changes: 13 additions & 0 deletions SafeExamBrowser.Client/ClientController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ private void RegisterEvents()
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;
Expand All @@ -198,6 +199,7 @@ private void RegisterEvents()
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<ITerminationActivator>())
Expand All @@ -211,6 +213,16 @@ private void RegisterEvents()
}
}

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;
Expand All @@ -226,6 +238,7 @@ private void DeregisterEvents()
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
Browser.SessionIdentifierDetected -= Browser_SessionIdentifierDetected;
Browser.TerminationRequested -= Browser_TerminationRequested;
Browser.LoseFocusRequested -= Browser_LoseFocusRequested;
}

if (ClientHost != null)
Expand Down
7 changes: 7 additions & 0 deletions SafeExamBrowser.I18n.Contracts/TextKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@ public enum TextKey
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,
Expand Down
21 changes: 21 additions & 0 deletions SafeExamBrowser.I18n/Data/de.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@
<Entry key="BrowserWindow_FindMenuItem">
Seite durchsuchen...
</Entry>
<Entry key="BrowserWindow_ReloadButton">
Neu laden
</Entry>
<Entry key="BrowserWindow_BackwardButton">
Rückwärts
</Entry>
<Entry key="BrowserWindow_ForwardButton">
Vorwärts
</Entry>
<Entry key="BrowserWindow_DownloadsButton">
Download
</Entry>
<Entry key="BrowserWindow_HomeButton">
Home
</Entry>
<Entry key="BrowserWindow_MenuButton">
Menü
</Entry>
<Entry key="BrowserWindow_UrlTextBox">
URL eingeben
</Entry>
<Entry key="BrowserWindow_ZoomMenuItem">
Seiten-Zoom
</Entry>
Expand Down
Loading