accessibility

This commit is contained in:
Jonas Sourlier 2021-11-24 08:42:07 +01:00
parent a4d1904b81
commit d040615c6e
17 changed files with 384 additions and 0 deletions

View file

@ -6,8 +6,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Browser.Contracts.Events; using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.Events;
namespace SafeExamBrowser.Browser.Contracts namespace SafeExamBrowser.Browser.Contracts
{ {
@ -30,5 +32,15 @@ namespace SafeExamBrowser.Browser.Contracts
/// Event fired when the browser application detects a request to terminate SEB. /// Event fired when the browser application detects a request to terminate SEB.
/// </summary> /// </summary>
event TerminationRequestedEventHandler TerminationRequested; 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.
/// </summary>
void Focus(bool forward);
} }
} }

View file

@ -75,6 +75,10 @@
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project> <Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name> <Name>SafeExamBrowser.Settings</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -27,6 +27,7 @@ using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser.Proxy; using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging; using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using SafeExamBrowser.WindowsApi.Contracts; using SafeExamBrowser.WindowsApi.Contracts;
@ -58,6 +59,7 @@ namespace SafeExamBrowser.Browser
public event DownloadRequestedEventHandler ConfigurationDownloadRequested; public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event TerminationRequestedEventHandler TerminationRequested; public event TerminationRequestedEventHandler TerminationRequested;
public event WindowsChangedEventHandler WindowsChanged; public event WindowsChangedEventHandler WindowsChanged;
@ -195,6 +197,7 @@ namespace SafeExamBrowser.Browser
window.ResetRequested += Window_ResetRequested; window.ResetRequested += Window_ResetRequested;
window.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i); window.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i);
window.TerminationRequested += () => TerminationRequested?.Invoke(); window.TerminationRequested += () => TerminationRequested?.Invoke();
window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
window.InitializeControl(); window.InitializeControl();
windows.Add(window); windows.Add(window);
@ -457,5 +460,13 @@ namespace SafeExamBrowser.Browser
CreateNewWindow(); CreateNewWindow();
logger.Info("Successfully reset browser."); logger.Info("Successfully reset browser.");
} }
public void Focus(bool forward)
{
windows.ForEach(window =>
{
window.Focus(forward);
});
}
} }
} }

View file

@ -143,5 +143,14 @@ namespace SafeExamBrowser.Browser
{ {
control.BrowserCore.SetZoomLevel(level); control.BrowserCore.SetZoomLevel(level);
} }
/// <summary>
/// Executes the given Javascript code in the browser.
/// </summary>
public async void ExecuteJavascript(string javascript, System.Action<dynamic> callback)
{
var result = await this.control.EvaluateScriptAsync(javascript);
callback(result);
}
} }
} }

View file

@ -31,6 +31,7 @@ using SafeExamBrowser.Settings.Browser.Filter;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data; using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using Syroot.Windows.IO; using Syroot.Windows.IO;
@ -80,6 +81,7 @@ namespace SafeExamBrowser.Browser
internal event PopupRequestedEventHandler PopupRequested; internal event PopupRequestedEventHandler PopupRequested;
internal event ResetRequestedEventHandler ResetRequested; internal event ResetRequestedEventHandler ResetRequested;
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
internal event TerminationRequestedEventHandler TerminationRequested; internal event TerminationRequestedEventHandler TerminationRequested;
public event IconChangedEventHandler IconChanged; public event IconChangedEventHandler IconChanged;
@ -162,6 +164,7 @@ namespace SafeExamBrowser.Browser
keyboardHandler.ZoomInRequested += ZoomInRequested; keyboardHandler.ZoomInRequested += ZoomInRequested;
keyboardHandler.ZoomOutRequested += ZoomOutRequested; keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested; keyboardHandler.ZoomResetRequested += ZoomResetRequested;
keyboardHandler.TabPressed += TabPressed;
resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id); resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited; requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked; requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
@ -198,6 +201,7 @@ namespace SafeExamBrowser.Browser
window.FindRequested += Window_FindRequested; window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested; window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested; window.HomeNavigationRequested += HomeNavigationRequested;
window.LoseFocusRequested += Window_LoseFocusRequested;
window.ReloadRequested += ReloadRequested; window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested; window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested; window.ZoomOutRequested += ZoomOutRequested;
@ -655,6 +659,11 @@ namespace SafeExamBrowser.Browser
Control.NavigateForwards(); Control.NavigateForwards();
} }
private void Window_LoseFocusRequested(bool forward)
{
LoseFocusRequested?.Invoke(forward);
}
private void ZoomInRequested() private void ZoomInRequested()
{ {
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300) if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
@ -688,6 +697,43 @@ namespace SafeExamBrowser.Browser
} }
} }
private void TabPressed(object sender, 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() private double CalculateZoomPercentage()
{ {
return (zoomLevel * 25.0) + 100.0; return (zoomLevel * 25.0) + 100.0;

View file

@ -20,6 +20,9 @@ namespace SafeExamBrowser.Browser.Handlers
internal event ActionRequestedEventHandler ZoomInRequested; internal event ActionRequestedEventHandler ZoomInRequested;
internal event ActionRequestedEventHandler ZoomOutRequested; internal event ActionRequestedEventHandler ZoomOutRequested;
internal event ActionRequestedEventHandler ZoomResetRequested; internal event ActionRequestedEventHandler ZoomResetRequested;
internal event System.EventHandler<bool> TabPressed;
private int? currentKeyDown = null;
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
{ {
@ -52,8 +55,14 @@ namespace SafeExamBrowser.Browser.Handlers
{ {
ZoomResetRequested?.Invoke(); ZoomResetRequested?.Invoke();
} }
if (keyCode == (int)Keys.Tab && keyCode == currentKeyDown)
{
TabPressed?.Invoke(this, shift);
}
} }
currentKeyDown = null;
return false; return false;
} }
@ -66,6 +75,11 @@ namespace SafeExamBrowser.Browser.Handlers
return true; return true;
} }
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
{
currentKeyDown = keyCode;
}
return false; return false;
} }
} }

View file

@ -188,6 +188,7 @@ namespace SafeExamBrowser.Client
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected; Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected;
Browser.TerminationRequested += Browser_TerminationRequested; Browser.TerminationRequested += Browser_TerminationRequested;
Browser.LoseFocusRequested += Browser_LoseFocusRequested; ;
ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested; ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested; ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested; ClientHost.PasswordRequested += ClientHost_PasswordRequested;
@ -198,6 +199,7 @@ namespace SafeExamBrowser.Client
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
runtime.ConnectionLost += Runtime_ConnectionLost; runtime.ConnectionLost += Runtime_ConnectionLost;
systemMonitor.SessionSwitched += SystemMonitor_SessionSwitched; systemMonitor.SessionSwitched += SystemMonitor_SessionSwitched;
taskbar.LoseFocusRequested += Taskbar_LoseFocusRequested;
taskbar.QuitButtonClicked += Shell_QuitButtonClicked; taskbar.QuitButtonClicked += Shell_QuitButtonClicked;
foreach (var activator in context.Activators.OfType<ITerminationActivator>()) foreach (var activator in context.Activators.OfType<ITerminationActivator>())
@ -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() private void DeregisterEvents()
{ {
actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked; actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked;

View file

@ -61,6 +61,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// </summary> /// </summary>
void Destroy(); void Destroy();
/// <summary>
/// Executes the given Javascript code in the browser.
/// </summary>
void ExecuteJavascript(string javascript, System.Action<dynamic> callback);
/// <summary> /// <summary>
/// Attempts to find the given term on the current page according to the specified parameters. /// Attempts to find the given term on the current page according to the specified parameters.
/// </summary> /// </summary>

View file

@ -10,6 +10,7 @@ using System;
using SafeExamBrowser.Core.Contracts.Resources.Icons; using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data; using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
namespace SafeExamBrowser.UserInterface.Contracts.Browser namespace SafeExamBrowser.UserInterface.Contracts.Browser
@ -64,6 +65,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// </summary> /// </summary>
event ActionRequestedEventHandler HomeNavigationRequested; event ActionRequestedEventHandler HomeNavigationRequested;
/// <summary>
/// Event fired when the browser window wants to lose focus to the taskbar.
/// </summary>
event LoseFocusRequestedEventHandler LoseFocusRequested;
/// <summary> /// <summary>
/// Event fired when the user would like to reload the current page. /// Event fired when the user would like to reload the current page.
/// </summary> /// </summary>
@ -123,5 +129,16 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// Updates the display value of the current page zoom. Value is expected to be in percentage. /// Updates the display value of the current page zoom. Value is expected to be in percentage.
/// </summary> /// </summary>
void UpdateZoomLevel(double value); void UpdateZoomLevel(double value);
/// <summary>
/// Sets the focus to the toolbar.
/// </summary>
/// <param name="forward">If true, the first focusable control on the toolbar gets focused. If false, the last one.</param>
void FocusToolbar(bool forward);
/// <summary>
/// Sets the focus to the browser.
/// </summary>
void FocusBrowser();
} }
} }

View file

@ -0,0 +1,15 @@
/*
* 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/.
*/
namespace SafeExamBrowser.UserInterface.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);
}

View file

@ -63,6 +63,7 @@
<Compile Include="Browser\IBrowserControl.cs" /> <Compile Include="Browser\IBrowserControl.cs" />
<Compile Include="Browser\IBrowserWindow.cs" /> <Compile Include="Browser\IBrowserWindow.cs" />
<Compile Include="Events\ActionRequestedEventHandler.cs" /> <Compile Include="Events\ActionRequestedEventHandler.cs" />
<Compile Include="Events\LoseFocusRequestedEventHandler.cs" />
<Compile Include="FileSystemDialog\FileSystemDialogResult.cs" /> <Compile Include="FileSystemDialog\FileSystemDialogResult.cs" />
<Compile Include="FileSystemDialog\FileSystemElement.cs" /> <Compile Include="FileSystemDialog\FileSystemElement.cs" />
<Compile Include="FileSystemDialog\FileSystemOperation.cs" /> <Compile Include="FileSystemDialog\FileSystemOperation.cs" />

View file

@ -7,6 +7,7 @@
*/ */
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Shell namespace SafeExamBrowser.UserInterface.Contracts.Shell
@ -31,6 +32,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// </summary> /// </summary>
event QuitButtonClickedEventHandler QuitButtonClicked; event QuitButtonClickedEventHandler QuitButtonClicked;
/// <summary>
/// Event fired when the Taskbar wants to lose focus.
/// </summary>
event LoseFocusRequestedEventHandler LoseFocusRequested;
/// <summary> /// <summary>
/// Adds the given application control to the taskbar. /// Adds the given application control to the taskbar.
/// </summary> /// </summary>
@ -70,5 +76,10 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// Shows the taskbar. /// Shows the taskbar.
/// </summary> /// </summary>
void Show(); void Show();
/// <summary>
/// Puts the focus on the taskbar.
/// </summary>
void Focus(bool forward = true);
} }
} }

View file

@ -16,11 +16,13 @@ using System.Windows.Interop;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Core.Contracts.Resources.Icons; using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data; using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events; using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Desktop.Controls.Browser; using SafeExamBrowser.UserInterface.Desktop.Controls.Browser;
@ -36,6 +38,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
private WindowClosedEventHandler closed; private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing; private WindowClosingEventHandler closing;
private bool browserControlGetsFocusFromTaskbar = false;
private WindowSettings WindowSettings private WindowSettings WindowSettings
{ {
@ -56,6 +59,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
public event ActionRequestedEventHandler ZoomInRequested; public event ActionRequestedEventHandler ZoomInRequested;
public event ActionRequestedEventHandler ZoomOutRequested; public event ActionRequestedEventHandler ZoomOutRequested;
public event ActionRequestedEventHandler ZoomResetRequested; public event ActionRequestedEventHandler ZoomResetRequested;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
event WindowClosedEventHandler IWindow.Closed event WindowClosedEventHandler IWindow.Closed
{ {
@ -199,6 +203,26 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
} }
} }
private void BrowserWindow_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab && Toolbar.IsKeyboardFocusWithin)
{
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
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;
}
}
}
}
}
private void BrowserWindow_KeyUp(object sender, KeyEventArgs e) private void BrowserWindow_KeyUp(object sender, KeyEventArgs e)
{ {
if (e.Key == Key.F5) if (e.Key == Key.F5)
@ -215,6 +239,21 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
ShowFindbar(); ShowFindbar();
} }
if (e.Key == Key.Tab)
{
if (BrowserControlHost.IsFocused && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
if (Findbar.Visibility == Visibility.Hidden || (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
Toolbar.Focus();
}
else if (Toolbar.Visibility == Visibility.Hidden)
{
Findbar.Focus();
}
}
}
} }
private void BrowserWindow_Loaded(object sender, RoutedEventArgs e) private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
@ -324,6 +363,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback); MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
KeyDown += BrowserWindow_KeyDown;
KeyUp += BrowserWindow_KeyUp; KeyUp += BrowserWindow_KeyUp;
LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; }; LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
ReloadButton.Click += (o, args) => ReloadRequested?.Invoke(); ReloadButton.Click += (o, args) => ReloadRequested?.Invoke();
@ -338,6 +378,41 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke(); ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke();
ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke(); ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke();
ZoomResetButton.Click += (o, args) => ZoomResetRequested?.Invoke(); ZoomResetButton.Click += (o, args) => ZoomResetRequested?.Invoke();
BrowserControlHost.GotKeyboardFocus += BrowserControlHost_GotKeyboardFocus;
}
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];
setTimeout(function () { item.focus(); }, 20);
}
}";
var control = BrowserControlHost.Child as IBrowserControl;
control.ExecuteJavascript(javascript, result =>
{
if (!result.Success)
{
//logger.Error($"Javascript error {result.Message}!");
}
});
control.ExecuteJavascript("__SEB_focusElement(" + forward.ToString().ToLower() + ")", result =>
{
if (!result.Success)
{
//logger.Error($"Javascript error {result.Message}!");
}
});
} }
private void ApplySettings() private void ApplySettings()
@ -444,5 +519,42 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem); FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem);
ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem); ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem);
} }
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;
}));
}
} }
} }

View file

@ -6,6 +6,7 @@
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar"
xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d" Title="Taskbar" Background="{DynamicResource BackgroundBrush}" Height="40" Width="750" WindowStyle="None" Topmost="True" 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"> ResizeMode="NoResize" Icon="../Images/SafeExamBrowser.ico">
<Window.Resources> <Window.Resources>
<ResourceDictionary> <ResourceDictionary>

View file

@ -6,10 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows; using System.Windows;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -20,6 +22,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
private bool allowClose; private bool allowClose;
private ILogger logger; private ILogger logger;
private bool isQuitButtonFocusedAtKeyDown;
private bool isFirstChildFocusedAtKeyDown;
public bool ShowClock public bool ShowClock
{ {
@ -31,6 +35,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); } set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
} }
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event QuitButtonClickedEventHandler QuitButtonClicked; public event QuitButtonClickedEventHandler QuitButtonClicked;
internal Taskbar(ILogger logger) internal Taskbar(ILogger logger)
@ -159,5 +164,82 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
Loaded += (o, args) => InitializeBounds(); Loaded += (o, args) => InitializeBounds();
QuitButton.Clicked += QuitButton_Clicked; 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;
}
} }
} }

View file

@ -21,6 +21,7 @@ using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data; using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events; using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Mobile.Controls.Browser; using SafeExamBrowser.UserInterface.Mobile.Controls.Browser;
@ -51,6 +52,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
public event ActionRequestedEventHandler DeveloperConsoleRequested; public event ActionRequestedEventHandler DeveloperConsoleRequested;
public event FindRequestedEventHandler FindRequested; public event FindRequestedEventHandler FindRequested;
public event ActionRequestedEventHandler ForwardNavigationRequested; public event ActionRequestedEventHandler ForwardNavigationRequested;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event ActionRequestedEventHandler HomeNavigationRequested; public event ActionRequestedEventHandler HomeNavigationRequested;
public event ActionRequestedEventHandler ReloadRequested; public event ActionRequestedEventHandler ReloadRequested;
public event ActionRequestedEventHandler ZoomInRequested; public event ActionRequestedEventHandler ZoomInRequested;
@ -454,5 +456,20 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem); FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem);
ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem); 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();
}
} }
} }

View file

@ -10,6 +10,7 @@ using System.ComponentModel;
using System.Windows; using System.Windows;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -31,6 +32,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); } set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
} }
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event QuitButtonClickedEventHandler QuitButtonClicked; public event QuitButtonClickedEventHandler QuitButtonClicked;
internal Taskbar(ILogger logger) internal Taskbar(ILogger logger)
@ -159,5 +161,18 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
Loaded += (o, args) => InitializeBounds(); Loaded += (o, args) => InitializeBounds();
QuitButton.Clicked += QuitButton_Clicked; QuitButton.Clicked += QuitButton_Clicked;
} }
void ITaskbar.Focus(bool fromTop)
{
base.Activate();
if (fromTop)
{
this.ApplicationStackPanel.Children[0].Focus();
}
else
{
this.QuitButton.Focus();
}
}
} }
} }