Update Safe Exam Browser Patch to 3.10.0.826
This commit is contained in:
@@ -18,6 +18,7 @@ using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Integrations;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
@@ -119,6 +120,7 @@ namespace SafeExamBrowser.Browser
|
||||
InitializeCookies();
|
||||
InitializeDownAndUploadDirectory();
|
||||
InitializeIntegrityKeys();
|
||||
InitializePreferences();
|
||||
|
||||
logger.Info("Initialized browser.");
|
||||
}
|
||||
@@ -167,6 +169,12 @@ namespace SafeExamBrowser.Browser
|
||||
private void CreateNewWindow(PopupRequestedEventArgs args = default)
|
||||
{
|
||||
var id = ++windowIdCounter;
|
||||
var integrations = new Integration[]
|
||||
{
|
||||
new GenericIntegration(logger.CloneFor($"{nameof(GenericIntegration)} #{id}")),
|
||||
new EdxIntegration(logger.CloneFor($"{nameof(EdxIntegration)} #{id}")),
|
||||
new MoodleIntegration(logger.CloneFor($"{nameof(MoodleIntegration)} #{id}"))
|
||||
};
|
||||
var isMainWindow = windows.Count == 0;
|
||||
var startUrl = GenerateStartUrl();
|
||||
var windowLogger = logger.CloneFor($"Browser Window #{id}");
|
||||
@@ -176,6 +184,7 @@ namespace SafeExamBrowser.Browser
|
||||
fileSystemDialog,
|
||||
hashAlgorithm,
|
||||
id,
|
||||
integrations,
|
||||
isMainWindow,
|
||||
keyGenerator,
|
||||
windowLogger,
|
||||
@@ -197,7 +206,7 @@ namespace SafeExamBrowser.Browser
|
||||
window.InitializeControl();
|
||||
windows.Add(window);
|
||||
|
||||
if (args != default(PopupRequestedEventArgs))
|
||||
if (args != default)
|
||||
{
|
||||
args.Window = window;
|
||||
}
|
||||
@@ -213,6 +222,7 @@ namespace SafeExamBrowser.Browser
|
||||
private void DeleteCookies()
|
||||
{
|
||||
var callback = new TaskDeleteCookiesCallback();
|
||||
var cookieManager = Cef.GetGlobalCookieManager();
|
||||
|
||||
callback.Task.ContinueWith(task =>
|
||||
{
|
||||
@@ -226,7 +236,7 @@ namespace SafeExamBrowser.Browser
|
||||
}
|
||||
});
|
||||
|
||||
if (Cef.GetGlobalCookieManager().DeleteCookies(callback: callback))
|
||||
if (cookieManager != default && cookieManager.DeleteCookies(callback: callback))
|
||||
{
|
||||
logger.Debug("Successfully initiated cookie deletion.");
|
||||
}
|
||||
@@ -316,7 +326,6 @@ namespace SafeExamBrowser.Browser
|
||||
|
||||
cefSettings.AcceptLanguageList = CultureInfo.CurrentUICulture.Name;
|
||||
cefSettings.CachePath = appConfig.BrowserCachePath;
|
||||
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
|
||||
cefSettings.LogFile = appConfig.BrowserLogFilePath;
|
||||
cefSettings.LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info);
|
||||
cefSettings.PersistSessionCookies = !settings.DeleteCookiesOnStartup || !settings.DeleteCookiesOnShutdown;
|
||||
@@ -339,6 +348,7 @@ namespace SafeExamBrowser.Browser
|
||||
|
||||
cefSettings.CefCommandLineArgs.Add("enable-media-stream");
|
||||
cefSettings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");
|
||||
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
|
||||
cefSettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");
|
||||
|
||||
InitializeProxySettings(cefSettings);
|
||||
@@ -417,6 +427,18 @@ namespace SafeExamBrowser.Browser
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializePreferences()
|
||||
{
|
||||
Cef.UIThreadTaskFactory.StartNew(() =>
|
||||
{
|
||||
using (var requestContext = Cef.GetGlobalRequestContext())
|
||||
{
|
||||
requestContext.SetPreference("autofill.credit_card_enabled", false, out _);
|
||||
requestContext.SetPreference("autofill.profile_enabled", false, out _);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeProxySettings(CefSettings cefSettings)
|
||||
{
|
||||
if (settings.Proxy.Policy == ProxyPolicy.Custom)
|
||||
@@ -506,6 +528,7 @@ namespace SafeExamBrowser.Browser
|
||||
private void Window_ResetRequested()
|
||||
{
|
||||
logger.Info("Attempting to reset browser...");
|
||||
|
||||
AwaitReady();
|
||||
|
||||
foreach (var window in windows)
|
||||
|
@@ -23,6 +23,7 @@ namespace SafeExamBrowser.Browser
|
||||
{
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly ICefSharpControl control;
|
||||
private readonly IContextMenuHandler contextMenuHandler;
|
||||
private readonly IDialogHandler dialogHandler;
|
||||
private readonly IDisplayHandler displayHandler;
|
||||
private readonly IDownloadHandler downloadHandler;
|
||||
@@ -47,6 +48,7 @@ namespace SafeExamBrowser.Browser
|
||||
public BrowserControl(
|
||||
Clipboard clipboard,
|
||||
ICefSharpControl control,
|
||||
IContextMenuHandler contextMenuHandler,
|
||||
IDialogHandler dialogHandler,
|
||||
IDisplayHandler displayHandler,
|
||||
IDownloadHandler downloadHandler,
|
||||
@@ -58,8 +60,9 @@ namespace SafeExamBrowser.Browser
|
||||
IRenderProcessMessageHandler renderProcessMessageHandler,
|
||||
IRequestHandler requestHandler)
|
||||
{
|
||||
this.control = control;
|
||||
this.clipboard = clipboard;
|
||||
this.control = control;
|
||||
this.contextMenuHandler = contextMenuHandler;
|
||||
this.dialogHandler = dialogHandler;
|
||||
this.displayHandler = displayHandler;
|
||||
this.downloadHandler = downloadHandler;
|
||||
@@ -89,31 +92,20 @@ namespace SafeExamBrowser.Browser
|
||||
{
|
||||
control.BrowserCore.EvaluateScriptAsync(code).ContinueWith(t =>
|
||||
{
|
||||
callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = t.Result.Message,
|
||||
Result = t.Result.Result,
|
||||
Success = t.Result.Success
|
||||
});
|
||||
callback?.Invoke(new JavaScriptResult { Message = t.Result.Message, Result = t.Result.Result, Success = t.Result.Success });
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = "JavaScript can't be executed in main frame!",
|
||||
Success = false
|
||||
}));
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult { Message = "Could not execute JavaScript in main frame!", Success = false }));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'!", e);
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = $"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'! Reason: {e.Message}",
|
||||
Success = false
|
||||
}));
|
||||
var message = "Failed to execute JavaScript in main frame!";
|
||||
|
||||
logger.Error(message, e);
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult { Message = $"{message} Reason: {e.Message}", Success = false }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,10 +121,13 @@ namespace SafeExamBrowser.Browser
|
||||
control.AddressChanged += (o, e) => AddressChanged?.Invoke(e.Address);
|
||||
control.AuthCredentialsRequired += (w, b, o, i, h, p, r, s, c, a) => a.Value = requestHandler.GetAuthCredentials(w, b, o, i, h, p, r, s, c);
|
||||
control.BeforeBrowse += (w, b, f, r, u, i, a) => a.Value = requestHandler.OnBeforeBrowse(w, b, f, r, u, i);
|
||||
control.BeforeContextMenu += (w, b, f, p, m) => contextMenuHandler.OnBeforeContextMenu(w, b, f, p, m);
|
||||
control.BeforeDownload += (w, b, d, c, a) => a.Value = a.Value = downloadHandler.OnBeforeDownload(w, b, d, c);
|
||||
control.BeforeUnloadDialog += (w, b, m, r, c, a) => a.Value = javaScriptDialogHandler.OnBeforeUnloadDialog(w, b, m, r, c);
|
||||
control.CanDownload += (w, b, u, r, a) => a.Value = downloadHandler.CanDownload(w, b, u, r);
|
||||
control.ContextCreated += (w, b, f) => renderProcessMessageHandler.OnContextCreated(w, b, f);
|
||||
control.ContextMenuCommand += (w, b, f, p, c, e, a) => a.Value = contextMenuHandler.OnContextMenuCommand(w, b, f, p, c, e);
|
||||
control.ContextMenuDismissed += (w, b, f) => contextMenuHandler.OnContextMenuDismissed(w, b, f);
|
||||
control.ContextReleased += (w, b, f) => renderProcessMessageHandler.OnContextReleased(w, b, f);
|
||||
control.DialogClosed += (w, b) => javaScriptDialogHandler.OnDialogClosed(w, b);
|
||||
control.DownloadUpdated += (w, b, d, c) => downloadHandler.OnDownloadUpdated(w, b, d, c);
|
||||
@@ -152,6 +147,7 @@ namespace SafeExamBrowser.Browser
|
||||
control.PreKeyEvent += (IWebBrowser w, IBrowser b, KeyType t, int k, int n, CefEventFlags m, bool i, ref bool s, GenericEventArgs a) => a.Value = keyboardHandler.OnPreKeyEvent(w, b, t, k, n, m, i, ref s);
|
||||
control.ResetDialogState += (w, b) => javaScriptDialogHandler.OnResetDialogState(w, b);
|
||||
control.ResourceRequestHandlerRequired += (IWebBrowser w, IBrowser b, IFrame f, IRequest r, bool n, bool d, string i, ref bool h, ResourceRequestEventArgs a) => a.Handler = requestHandler.GetResourceRequestHandler(w, b, f, r, n, d, i, ref h);
|
||||
control.RunContextMenu += (w, b, f, p, m, c, a) => a.Value = contextMenuHandler.RunContextMenu(w, b, f, p, m, c);
|
||||
control.SetFocus += (w, b, s, a) => a.Value = focusHandler.OnSetFocus(w, b, s);
|
||||
control.TakeFocus += (w, b, n) => focusHandler.OnTakeFocus(w, b, n);
|
||||
control.TitleChanged += (o, e) => TitleChanged?.Invoke(e.Title);
|
||||
@@ -193,9 +189,21 @@ namespace SafeExamBrowser.Browser
|
||||
control.BrowserCore.SetZoomLevel(level);
|
||||
}
|
||||
|
||||
private void Clipboard_Changed(long id)
|
||||
private void Clipboard_Changed(string id)
|
||||
{
|
||||
ExecuteJavaScript($"SafeExamBrowser.clipboard.update({id}, '{clipboard.Content}');");
|
||||
try
|
||||
{
|
||||
var script = $"SafeExamBrowser.clipboard.update('{id}', '{clipboard.Content}');";
|
||||
|
||||
foreach (var frame in control.BrowserCore?.GetAllFrames() ?? Enumerable.Empty<IFrame>())
|
||||
{
|
||||
frame.EvaluateScriptAsync(script);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to update JavaScript clipboard!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void Control_IsBrowserInitializedChanged(object sender, EventArgs e)
|
||||
@@ -208,7 +216,7 @@ namespace SafeExamBrowser.Browser
|
||||
|
||||
private void WebBrowser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
|
||||
{
|
||||
clipboard.Process(e);
|
||||
clipboard.Update(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Filters;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Browser.Integrations;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
@@ -52,6 +53,7 @@ namespace SafeExamBrowser.Browser
|
||||
private readonly IFileSystemDialog fileSystemDialog;
|
||||
private readonly IHashAlgorithm hashAlgorithm;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly IEnumerable<Integration> integrations;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly IModuleLogger logger;
|
||||
private readonly IMessageBox messageBox;
|
||||
@@ -97,6 +99,7 @@ namespace SafeExamBrowser.Browser
|
||||
IFileSystemDialog fileSystemDialog,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
int id,
|
||||
IEnumerable<Integration> integrations,
|
||||
bool isMainWindow,
|
||||
IKeyGenerator keyGenerator,
|
||||
IModuleLogger logger,
|
||||
@@ -113,6 +116,7 @@ namespace SafeExamBrowser.Browser
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.httpClient = new HttpClient();
|
||||
this.Id = id;
|
||||
this.integrations = integrations;
|
||||
this.IsMainWindow = isMainWindow;
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
@@ -153,6 +157,7 @@ namespace SafeExamBrowser.Browser
|
||||
{
|
||||
var cefSharpControl = default(ICefSharpControl);
|
||||
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} #{Id}");
|
||||
var contextMenuHandler = new ContextMenuHandler();
|
||||
var dialogHandler = new DialogHandler();
|
||||
var displayHandler = new DisplayHandler();
|
||||
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
|
||||
@@ -164,7 +169,7 @@ namespace SafeExamBrowser.Browser
|
||||
var renderHandler = new RenderProcessMessageHandler(appConfig, clipboard, keyGenerator, settings, text);
|
||||
var requestFilter = new RequestFilter();
|
||||
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
|
||||
var resourceHandler = new ResourceHandler(appConfig, requestFilter, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
|
||||
var resourceHandler = new ResourceHandler(appConfig, requestFilter, integrations, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
|
||||
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings);
|
||||
|
||||
Icon = new BrowserIconResource();
|
||||
@@ -202,6 +207,7 @@ namespace SafeExamBrowser.Browser
|
||||
Control = new BrowserControl(
|
||||
clipboard,
|
||||
cefSharpControl,
|
||||
contextMenuHandler,
|
||||
dialogHandler,
|
||||
displayHandler,
|
||||
downloadHandler,
|
||||
@@ -218,12 +224,13 @@ namespace SafeExamBrowser.Browser
|
||||
Control.TitleChanged += Control_TitleChanged;
|
||||
|
||||
Control.Initialize();
|
||||
|
||||
logger.Debug("Initialized browser control.");
|
||||
}
|
||||
|
||||
internal void InitializeWindow()
|
||||
{
|
||||
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, this.logger);
|
||||
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, logger);
|
||||
window.AddressChanged += Window_AddressChanged;
|
||||
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
|
||||
window.Closed += Window_Closed;
|
||||
@@ -242,6 +249,7 @@ namespace SafeExamBrowser.Browser
|
||||
window.BringToForeground();
|
||||
|
||||
Handle = window.Handle;
|
||||
InitiateCookieTraversal();
|
||||
|
||||
logger.Debug("Initialized browser window.");
|
||||
}
|
||||
@@ -256,6 +264,22 @@ namespace SafeExamBrowser.Browser
|
||||
.Build();
|
||||
}
|
||||
|
||||
private void InitiateCookieTraversal()
|
||||
{
|
||||
var visitor = new CookieVisitor(integrations);
|
||||
|
||||
visitor.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
|
||||
|
||||
if (Cef.GetGlobalCookieManager().VisitAllCookies(visitor))
|
||||
{
|
||||
logger.Debug("Successfully initiated cookie traversal.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to initiate cookie traversal!");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeRequestFilter(IRequestFilter requestFilter)
|
||||
{
|
||||
if (settings.Filter.ProcessContentRequests || settings.Filter.ProcessMainRequests)
|
||||
@@ -297,8 +321,6 @@ namespace SafeExamBrowser.Browser
|
||||
window.UpdateTitle(address);
|
||||
TitleChanged?.Invoke(address);
|
||||
}
|
||||
|
||||
AutoFind();
|
||||
}
|
||||
|
||||
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
|
||||
@@ -834,14 +856,6 @@ namespace SafeExamBrowser.Browser
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoFind()
|
||||
{
|
||||
if (settings.AllowFind && !string.IsNullOrEmpty(findParameters.term) && !CLEAR_FIND_TERM.Equals(findParameters.term, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Control.Find(findParameters.term, findParameters.isInitial, findParameters.caseSensitive, findParameters.forward);
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateZoomPercentage()
|
||||
{
|
||||
return (zoomLevel * 25.0) + 100.0;
|
||||
|
@@ -30,7 +30,7 @@ namespace SafeExamBrowser.Browser
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
internal void Process(JavascriptMessageReceivedEventArgs message)
|
||||
internal void Update(JavascriptMessageReceivedEventArgs message)
|
||||
{
|
||||
if (settings.UseIsolatedClipboard)
|
||||
{
|
||||
@@ -65,7 +65,7 @@ namespace SafeExamBrowser.Browser
|
||||
private class Data
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public long Id { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
||||
|
@@ -5,191 +5,207 @@
|
||||
* 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/.
|
||||
*
|
||||
* Original code taken and slightly adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
|
||||
* Original code taken and adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
|
||||
*/
|
||||
|
||||
SafeExamBrowser.clipboard = {
|
||||
id: Math.round((Date.now() + Math.random()) * 1000),
|
||||
ranges: [],
|
||||
text: "",
|
||||
|
||||
clear: function () {
|
||||
this.ranges = [];
|
||||
this.text = "";
|
||||
},
|
||||
|
||||
getContentEncoded: function () {
|
||||
var bytes = new TextEncoder().encode(this.text);
|
||||
var base64 = btoa(String.fromCodePoint(...bytes));
|
||||
|
||||
return base64;
|
||||
},
|
||||
|
||||
update: function (id, base64) {
|
||||
if (this.id != id) {
|
||||
var bytes = Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
|
||||
var content = new TextDecoder().decode(bytes);
|
||||
if (typeof SafeExamBrowser.clipboard === 'undefined') {
|
||||
SafeExamBrowser.clipboard = {
|
||||
id: crypto.randomUUID(),
|
||||
ranges: [],
|
||||
text: "",
|
||||
|
||||
clear: function () {
|
||||
this.ranges = [];
|
||||
this.text = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.text = "";
|
||||
},
|
||||
|
||||
function copySelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
|
||||
SafeExamBrowser.clipboard.ranges = [];
|
||||
} else {
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
var text = "";
|
||||
getContentEncoded: function () {
|
||||
var bytes = new TextEncoder().encode(this.text);
|
||||
var base64 = btoa(String.fromCodePoint(...bytes));
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
|
||||
text += SafeExamBrowser.clipboard.ranges[i].textContent;
|
||||
}
|
||||
return base64;
|
||||
},
|
||||
|
||||
SafeExamBrowser.clipboard.text = text;
|
||||
}
|
||||
}
|
||||
update: function (id, base64) {
|
||||
if (this.id != id) {
|
||||
var bytes = Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
|
||||
var content = new TextDecoder().decode(bytes);
|
||||
|
||||
function cutSelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
} else {
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var range = selection.getRangeAt(i);
|
||||
|
||||
if (designMode === 'on') {
|
||||
range.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
range.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.ranges = [];
|
||||
this.text = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pasteSelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
|
||||
} else {
|
||||
var w = e.target.ownerDocument.defaultView;
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = w.getSelection();
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var r = selection.getRangeAt(i);
|
||||
|
||||
if (designMode === 'on') {
|
||||
r.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(r.commonAncestorContainer)) {
|
||||
r.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (designMode === 'on') {
|
||||
var range = w.getSelection().getRangeAt(0);
|
||||
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range.collapse();
|
||||
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
if (typeof copySelection === 'undefined') {
|
||||
function copySelection(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
|
||||
SafeExamBrowser.clipboard.ranges = [];
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
var range = w.getSelection().getRangeAt(0);
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
var text = "";
|
||||
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
|
||||
text += SafeExamBrowser.clipboard.ranges[i].textContent;
|
||||
}
|
||||
|
||||
SafeExamBrowser.clipboard.text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof cutSelection === 'undefined') {
|
||||
function cutSelection(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
} else {
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var range = selection.getRangeAt(i);
|
||||
|
||||
if (designMode === 'on') {
|
||||
range.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
range.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCopy(e) {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
if (typeof pasteContent === 'undefined') {
|
||||
function pasteContent(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
|
||||
} else {
|
||||
var targetWindow = e.target.ownerDocument.defaultView;
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = targetWindow.getSelection();
|
||||
|
||||
try {
|
||||
copySelectedData(e);
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var r = selection.getRangeAt(i);
|
||||
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
if (designMode === 'on') {
|
||||
r.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(r.commonAncestorContainer)) {
|
||||
r.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (designMode === 'on') {
|
||||
var range = targetWindow.getSelection().getRangeAt(0);
|
||||
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = targetWindow.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range.collapse();
|
||||
range.insertNode(targetWindow.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
} else {
|
||||
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
var range = targetWindow.getSelection().getRangeAt(0);
|
||||
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = targetWindow.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range = targetWindow.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
range.insertNode(targetWindow.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function onCut(e) {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
if (typeof onCopy === 'undefined') {
|
||||
function onCopy(e) {
|
||||
try {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
|
||||
try {
|
||||
copySelectedData(e);
|
||||
cutSelectedData(e);
|
||||
copySelection(e);
|
||||
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
window.document.addEventListener("copy", onCopy, true);
|
||||
}
|
||||
|
||||
function onPaste(e) {
|
||||
try {
|
||||
pasteSelectedData(e);
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
if (typeof onCut === 'undefined') {
|
||||
function onCut(e) {
|
||||
try {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
|
||||
copySelection(e);
|
||||
cutSelection(e);
|
||||
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
window.document.addEventListener("cut", onCut, true);
|
||||
}
|
||||
|
||||
window.document.addEventListener("copy", onCopy, true);
|
||||
window.document.addEventListener("cut", onCut, true);
|
||||
window.document.addEventListener("paste", onPaste, true);
|
||||
|
||||
if (typeof onPaste === 'undefined') {
|
||||
function onPaste(e) {
|
||||
try {
|
||||
pasteContent(e);
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
window.document.addEventListener("paste", onPaste, true);
|
||||
}
|
||||
|
@@ -8,5 +8,5 @@
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void ClipboardChangedEventHandler(long id);
|
||||
internal delegate void ClipboardChangedEventHandler(string id);
|
||||
}
|
||||
|
@@ -12,21 +12,21 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class ContextMenuHandler : IContextMenuHandler
|
||||
{
|
||||
public void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
|
||||
public void OnBeforeContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
|
||||
{
|
||||
model.Clear();
|
||||
}
|
||||
|
||||
public bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
|
||||
public bool OnContextMenuCommand(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame)
|
||||
public void OnContextMenuDismissed(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
public bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
|
||||
public bool RunContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
49
SafeExamBrowser.Browser/Handlers/CookieVisitor.cs
Normal file
49
SafeExamBrowser.Browser/Handlers/CookieVisitor.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Integrations;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class CookieVisitor : ICookieVisitor
|
||||
{
|
||||
private readonly IEnumerable<Integration> integrations;
|
||||
|
||||
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
|
||||
internal CookieVisitor(IEnumerable<Integration> integrations)
|
||||
{
|
||||
this.integrations = integrations;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Visit(Cookie cookie, int count, int total, ref bool deleteCookie)
|
||||
{
|
||||
foreach (var integration in integrations)
|
||||
{
|
||||
var success = integration.TrySearchUserIdentifier(cookie, out var userIdentifier);
|
||||
|
||||
if (success)
|
||||
{
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
public bool OnDragEnter(IWebBrowser chromiumWebBrowser, IBrowser browser, IDragData dragData, DragOperationsMask mask)
|
||||
{
|
||||
return true;
|
||||
return !(dragData.IsFragment && mask.HasFlag(DragOperationsMask.Move));
|
||||
}
|
||||
|
||||
public void OnDraggableRegionsChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IList<DraggableRegion> regions)
|
||||
|
@@ -7,18 +7,16 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SafeExamBrowser.Browser.Content;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Integrations;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
@@ -36,6 +34,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ContentLoader contentLoader;
|
||||
private readonly IRequestFilter filter;
|
||||
private readonly IEnumerable<Integration> integrations;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly ILogger logger;
|
||||
private readonly SessionMode sessionMode;
|
||||
@@ -44,13 +43,13 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||
|
||||
private IResourceHandler contentHandler;
|
||||
private IResourceHandler pageHandler;
|
||||
private string userIdentifier;
|
||||
|
||||
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
|
||||
internal ResourceHandler(
|
||||
AppConfig appConfig,
|
||||
IRequestFilter filter,
|
||||
IEnumerable<Integration> integrations,
|
||||
IKeyGenerator keyGenerator,
|
||||
ILogger logger,
|
||||
SessionMode sessionMode,
|
||||
@@ -60,6 +59,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.filter = filter;
|
||||
this.integrations = integrations;
|
||||
this.contentLoader = new ContentLoader(text);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
@@ -235,217 +235,17 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||
|
||||
private void SearchUserIdentifier(IRequest request, IResponse response)
|
||||
{
|
||||
var success = TrySearchGenericUserIdentifier(response);
|
||||
|
||||
if (!success)
|
||||
foreach (var integration in integrations)
|
||||
{
|
||||
success = TrySearchEdxUserIdentifier(response);
|
||||
}
|
||||
var success = integration.TrySearchUserIdentifier(request, response, out var userIdentifier);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
TrySearchMoodleUserIdentifier(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySearchGenericUserIdentifier(IResponse response)
|
||||
{
|
||||
var ids = response.Headers.GetValues("X-LMS-USER-ID");
|
||||
var success = false;
|
||||
|
||||
if (ids != default(string[]))
|
||||
{
|
||||
var userId = ids.FirstOrDefault();
|
||||
|
||||
if (userId != default && userIdentifier != userId)
|
||||
if (success)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("Generic LMS user identifier detected.");
|
||||
success = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchEdxUserIdentifier(IResponse response)
|
||||
{
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var success = false;
|
||||
|
||||
if (cookies != default(string[]))
|
||||
{
|
||||
try
|
||||
{
|
||||
var userInfo = cookies.FirstOrDefault(c => c.Contains("edx-user-info"));
|
||||
|
||||
if (userInfo != default)
|
||||
{
|
||||
var start = userInfo.IndexOf("=") + 1;
|
||||
var end = userInfo.IndexOf("; expires");
|
||||
var cookie = userInfo.Substring(start, end - start);
|
||||
var sanitized = cookie.Replace("\\\"", "\"").Replace("\\054", ",").Trim('"');
|
||||
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
|
||||
var userName = json["username"].Value<string>();
|
||||
|
||||
if (userIdentifier != userName)
|
||||
{
|
||||
userIdentifier = userName;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("EdX user identifier detected.");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse edX user identifier!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifier(IRequest request, IResponse response)
|
||||
{
|
||||
var success = TrySearchMoodleUserIdentifierByLocation(response);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Plugin, request, response);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Theme, request, response);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifierByLocation(IResponse response)
|
||||
{
|
||||
var locations = response.Headers.GetValues("Location");
|
||||
|
||||
if (locations != default(string[]))
|
||||
{
|
||||
try
|
||||
{
|
||||
var location = locations.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
|
||||
|
||||
if (location != default)
|
||||
{
|
||||
var userId = location.Substring(location.IndexOf("=") + 1);
|
||||
|
||||
if (userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("Moodle user identifier detected by location.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse Moodle user identifier by location!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifierByRequest(MoodleRequestType type, IRequest request, IResponse response)
|
||||
{
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var success = false;
|
||||
|
||||
if (cookies != default(string[]))
|
||||
{
|
||||
var session = cookies.FirstOrDefault(c => c.Contains("MoodleSession"));
|
||||
|
||||
if (session != default)
|
||||
{
|
||||
var userId = ExecuteMoodleUserIdentifierRequest(request.Url, session, type);
|
||||
|
||||
if (int.TryParse(userId, out var id) && id > 0 && userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info($"Moodle user identifier detected by request ({type}).");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private string ExecuteMoodleUserIdentifierRequest(string requestUrl, string session, MoodleRequestType type)
|
||||
{
|
||||
var userId = default(string);
|
||||
|
||||
try
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var endpointUrl = default(string);
|
||||
var start = session.IndexOf("=") + 1;
|
||||
var end = session.IndexOf(";");
|
||||
var name = session.Substring(0, start - 1);
|
||||
var value = session.Substring(start, end - start);
|
||||
var uri = new Uri(requestUrl);
|
||||
|
||||
if (type == MoodleRequestType.Plugin)
|
||||
{
|
||||
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/mod/quiz/accessrule/sebserver/classes/external/user.php";
|
||||
}
|
||||
else
|
||||
{
|
||||
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/theme/boost_ethz/sebuser.php";
|
||||
}
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, endpointUrl);
|
||||
|
||||
using (var handler = new HttpClientHandler { UseCookies = false })
|
||||
using (var client = new HttpClient(handler))
|
||||
{
|
||||
message.Headers.Add("Cookie", $"{name}={value}");
|
||||
|
||||
var result = await client.SendAsync(message);
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
userId = await result.Content.ReadAsStringAsync();
|
||||
}
|
||||
else if (result.StatusCode != HttpStatusCode.NotFound)
|
||||
{
|
||||
logger.Error($"Failed to retrieve Moodle user identifier by request ({type})! Response: {(int) result.StatusCode} {result.ReasonPhrase}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to parse Moodle user identifier by request ({type})!", e);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to execute Moodle user identifier request ({type})!", e);
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
private enum MoodleRequestType
|
||||
{
|
||||
Plugin,
|
||||
Theme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
75
SafeExamBrowser.Browser/Integrations/EdxIntegration.cs
Normal file
75
SafeExamBrowser.Browser/Integrations/EdxIntegration.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CefSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Integrations
|
||||
{
|
||||
internal class EdxIntegration : Integration
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
public EdxIntegration(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
internal override bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier)
|
||||
{
|
||||
userIdentifier = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier)
|
||||
{
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var userInfo = cookies?.FirstOrDefault(c => c.Contains("edx-user-info"));
|
||||
|
||||
userIdentifier = default;
|
||||
|
||||
if (TryParseCookie(userInfo, out var id) && HasChanged(id))
|
||||
{
|
||||
userIdentifier = id;
|
||||
logger.Info($"User identifier '{id}' detected by session cookie on response.");
|
||||
}
|
||||
|
||||
return userIdentifier != default;
|
||||
}
|
||||
|
||||
private bool TryParseCookie(string userInfo, out string userIdentifier)
|
||||
{
|
||||
userIdentifier = default;
|
||||
|
||||
try
|
||||
{
|
||||
if (userInfo != default)
|
||||
{
|
||||
var start = userInfo.IndexOf("=") + 1;
|
||||
var end = userInfo.IndexOf("; expires");
|
||||
var cookie = userInfo.Substring(start, end - start);
|
||||
var sanitized = cookie.Replace("\\\"", "\"").Replace("\\054", ",").Trim('"');
|
||||
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
|
||||
|
||||
userIdentifier = json["username"].Value<string>();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse user identifier!", e);
|
||||
}
|
||||
|
||||
return userIdentifier != default;
|
||||
}
|
||||
}
|
||||
}
|
47
SafeExamBrowser.Browser/Integrations/GenericIntegration.cs
Normal file
47
SafeExamBrowser.Browser/Integrations/GenericIntegration.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Integrations
|
||||
{
|
||||
internal class GenericIntegration : Integration
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
public GenericIntegration(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
internal override bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier)
|
||||
{
|
||||
userIdentifier = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier)
|
||||
{
|
||||
var ids = response.Headers.GetValues("X-LMS-USER-ID");
|
||||
var id = ids?.FirstOrDefault();
|
||||
|
||||
userIdentifier = default;
|
||||
|
||||
if (HasChanged(id))
|
||||
{
|
||||
userIdentifier = id;
|
||||
logger.Info($"User identifier '{id}' detected by header of response.");
|
||||
}
|
||||
|
||||
return userIdentifier != default;
|
||||
}
|
||||
}
|
||||
}
|
32
SafeExamBrowser.Browser/Integrations/Integration.cs
Normal file
32
SafeExamBrowser.Browser/Integrations/Integration.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Integrations
|
||||
{
|
||||
internal abstract class Integration
|
||||
{
|
||||
private static string activeUserIdentifier;
|
||||
|
||||
internal abstract bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier);
|
||||
internal abstract bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier);
|
||||
|
||||
protected bool HasChanged(string userIdentifier)
|
||||
{
|
||||
var current = activeUserIdentifier;
|
||||
|
||||
if (userIdentifier != default && activeUserIdentifier != userIdentifier)
|
||||
{
|
||||
activeUserIdentifier = userIdentifier;
|
||||
}
|
||||
|
||||
return activeUserIdentifier != current;
|
||||
}
|
||||
}
|
||||
}
|
226
SafeExamBrowser.Browser/Integrations/MoodleIntegration.cs
Normal file
226
SafeExamBrowser.Browser/Integrations/MoodleIntegration.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using Cookie = CefSharp.Cookie;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Integrations
|
||||
{
|
||||
internal class MoodleIntegration : Integration
|
||||
{
|
||||
private const string PLUGIN_PATH = "/mod/quiz/accessrule/sebserver/classes/external/user.php";
|
||||
private const string SESSION_COOKIE_NAME = "MoodleSession";
|
||||
private const string THEME_PATH = "/theme/boost_ethz/sebuser.php";
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
public MoodleIntegration(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
internal override bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier)
|
||||
{
|
||||
return TrySearchByCookie(cookie, out userIdentifier);
|
||||
}
|
||||
|
||||
internal override bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier)
|
||||
{
|
||||
var success = TrySearchByLocation(response, out userIdentifier);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchByRequests(request, response, out userIdentifier);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchByCookie(Cookie cookie, out string userIdentifier)
|
||||
{
|
||||
var id = default(string);
|
||||
var type = default(RequestType);
|
||||
var isSession = cookie.Name.Contains(SESSION_COOKIE_NAME);
|
||||
var url = $"{(cookie.Secure ? Uri.UriSchemeHttps : Uri.UriSchemeHttp)}{Uri.SchemeDelimiter}{cookie.Domain}{cookie.Path}";
|
||||
var hasId = isSession && TryExecuteRequests(url, (cookie.Name, cookie.Value), out type, out id);
|
||||
|
||||
userIdentifier = default;
|
||||
|
||||
if (hasId && HasChanged(id))
|
||||
{
|
||||
userIdentifier = id;
|
||||
logger.Info($"User identifier '{id}' detected by request on cookie traversal ({type}).");
|
||||
}
|
||||
|
||||
return userIdentifier != default;
|
||||
}
|
||||
|
||||
private bool TrySearchByLocation(IResponse response, out string userIdentifier)
|
||||
{
|
||||
var locations = response.Headers.GetValues("Location");
|
||||
var location = locations?.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
|
||||
|
||||
userIdentifier = default;
|
||||
|
||||
if (TryParseLocation(location, out var id) && HasChanged(id))
|
||||
{
|
||||
userIdentifier = id;
|
||||
logger.Info($"User identifier '{id}' detected by location header of response.");
|
||||
}
|
||||
|
||||
return userIdentifier != default;
|
||||
}
|
||||
|
||||
private bool TrySearchByRequests(IRequest request, IResponse response, out string userIdentifier)
|
||||
{
|
||||
var id = default(string);
|
||||
var type = default(RequestType);
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var session = cookies?.FirstOrDefault(c => c.Contains(SESSION_COOKIE_NAME));
|
||||
var hasCookie = TryParseCookie(session, out var cookie);
|
||||
var hasId = hasCookie && TryExecuteRequests(request.Url, cookie, out type, out id);
|
||||
|
||||
userIdentifier = default;
|
||||
|
||||
if (hasId && HasChanged(id))
|
||||
{
|
||||
userIdentifier = id;
|
||||
logger.Info($"User identifier '{id}' detected by request on response ({type}).");
|
||||
}
|
||||
|
||||
return userIdentifier != default;
|
||||
}
|
||||
|
||||
private bool TryExecuteRequests(string originUrl, (string name, string value) session, out RequestType requestType, out string userId)
|
||||
{
|
||||
var order = new[] { RequestType.Plugin, RequestType.Theme };
|
||||
|
||||
requestType = default;
|
||||
userId = default;
|
||||
|
||||
foreach (var type in order)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = BuildUrl(originUrl, type);
|
||||
|
||||
using (var response = ExecuteRequest(url, session))
|
||||
{
|
||||
if (TryParseResponse(response, type, out var id))
|
||||
{
|
||||
requestType = type;
|
||||
userId = id;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to execute user identifier request ({type})!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return userId != default;
|
||||
}
|
||||
|
||||
private string BuildUrl(string originUrl, RequestType type)
|
||||
{
|
||||
var uri = new Uri(originUrl);
|
||||
var endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}{(type == RequestType.Plugin ? PLUGIN_PATH : THEME_PATH)}";
|
||||
|
||||
return endpointUrl;
|
||||
}
|
||||
|
||||
private HttpResponseMessage ExecuteRequest(string url, (string name, string value) session)
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, url))
|
||||
using (var handler = new HttpClientHandler { UseCookies = false })
|
||||
using (var client = new HttpClient(handler))
|
||||
{
|
||||
message.Headers.Add("Cookie", $"{session.name}={session.value}");
|
||||
|
||||
return client.SendAsync(message).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryParseCookie(string session, out (string name, string value) cookie)
|
||||
{
|
||||
cookie = default;
|
||||
|
||||
try
|
||||
{
|
||||
if (session != default)
|
||||
{
|
||||
var start = session.IndexOf("=") + 1;
|
||||
var end = session.IndexOf(";");
|
||||
|
||||
cookie.name = session.Substring(0, start - 1);
|
||||
cookie.value = session.Substring(start, end - start);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse session cookie!", e);
|
||||
}
|
||||
|
||||
return cookie.name != default && cookie.value != default;
|
||||
}
|
||||
|
||||
private bool TryParseLocation(string location, out string userId)
|
||||
{
|
||||
userId = default;
|
||||
|
||||
try
|
||||
{
|
||||
if (location != default)
|
||||
{
|
||||
userId = location.Substring(location.IndexOf("=") + 1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse location!", e);
|
||||
}
|
||||
|
||||
return userId != default;
|
||||
}
|
||||
|
||||
private bool TryParseResponse(HttpResponseMessage response, RequestType type, out string userId)
|
||||
{
|
||||
userId = default;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
|
||||
if (int.TryParse(content, out var id) && id > 0)
|
||||
{
|
||||
userId = content;
|
||||
}
|
||||
}
|
||||
else if (response.StatusCode != HttpStatusCode.NotFound)
|
||||
{
|
||||
logger.Error($"Failed to retrieve user identifier by request ({type})! Response: {(int) response.StatusCode} {response.ReasonPhrase}");
|
||||
}
|
||||
|
||||
return userId != default;
|
||||
}
|
||||
|
||||
private enum RequestType
|
||||
{
|
||||
Plugin,
|
||||
Theme
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x86.131.3.5\build\chromiumembeddedframework.runtime.win-x86.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x86.131.3.5\build\chromiumembeddedframework.runtime.win-x86.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x64.131.3.5\build\chromiumembeddedframework.runtime.win-x64.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x64.131.3.5\build\chromiumembeddedframework.runtime.win-x64.props')" />
|
||||
<Import Project="..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -53,14 +53,14 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CefSharp, Version=131.3.50.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.Common.131.3.50\lib\net462\CefSharp.dll</HintPath>
|
||||
<Reference Include="CefSharp, Version=139.0.280.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.Common.139.0.280\lib\net462\CefSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CefSharp.Core, Version=131.3.50.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.Common.131.3.50\lib\net462\CefSharp.Core.dll</HintPath>
|
||||
<Reference Include="CefSharp.Core, Version=139.0.280.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.Common.139.0.280\lib\net462\CefSharp.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CefSharp.WinForms, Version=131.3.50.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.WinForms.131.3.50\lib\net462\CefSharp.WinForms.dll</HintPath>
|
||||
<Reference Include="CefSharp.WinForms, Version=139.0.280.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.WinForms.139.0.280\lib\net462\CefSharp.WinForms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
@@ -103,6 +103,7 @@
|
||||
<Compile Include="Handlers\ContextMenuHandler.cs" />
|
||||
<Compile Include="BrowserControl.cs" />
|
||||
<Compile Include="BrowserIconResource.cs" />
|
||||
<Compile Include="Handlers\CookieVisitor.cs" />
|
||||
<Compile Include="Handlers\DialogHandler.cs" />
|
||||
<Compile Include="Handlers\DisplayHandler.cs" />
|
||||
<Compile Include="Handlers\DownloadHandler.cs" />
|
||||
@@ -113,6 +114,10 @@
|
||||
<Compile Include="Handlers\RequestHandler.cs" />
|
||||
<Compile Include="Handlers\ResourceHandler.cs" />
|
||||
<Compile Include="Content\ContentLoader.cs" />
|
||||
<Compile Include="Integrations\EdxIntegration.cs" />
|
||||
<Compile Include="Integrations\GenericIntegration.cs" />
|
||||
<Compile Include="Integrations\Integration.cs" />
|
||||
<Compile Include="Integrations\MoodleIntegration.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Wrapper\CefSharpBrowserControl.cs">
|
||||
<SubType>Component</SubType>
|
||||
@@ -122,10 +127,13 @@
|
||||
</Compile>
|
||||
<Compile Include="Wrapper\Events\AuthCredentialsEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\BeforeBrowseEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\BeforeContextMenuEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\BeforeDownloadEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\BeforeUnloadDialogEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\CanDownloadEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ContextCreatedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ContextMenuCommandEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ContextMenuDismissedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ContextReleasedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\DialogClosedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\DownloadUpdatedEventHandler.cs" />
|
||||
@@ -144,10 +152,12 @@
|
||||
<Compile Include="Wrapper\Events\ResetDialogStateEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ResourceRequestEventArgs.cs" />
|
||||
<Compile Include="Wrapper\Events\ResourceRequestEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\RunContextMenuEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\SetFocusEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\TakeFocusEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\UncaughtExceptionEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Extensions.cs" />
|
||||
<Compile Include="Wrapper\Handlers\ContextMenuHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\DialogHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\DisplayHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\DownloadHandlerSwitch.cs" />
|
||||
@@ -227,10 +237,10 @@
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x64.131.3.5\build\chromiumembeddedframework.runtime.win-x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x64.131.3.5\build\chromiumembeddedframework.runtime.win-x64.props'))" />
|
||||
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x86.131.3.5\build\chromiumembeddedframework.runtime.win-x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x86.131.3.5\build\chromiumembeddedframework.runtime.win-x86.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props'))" />
|
||||
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.131.3.50\build\CefSharp.Common.targets')" />
|
||||
<Import Project="..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets')" />
|
||||
</Project>
|
@@ -10,7 +10,6 @@ using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
using SafeExamBrowser.Browser.Wrapper.Handlers;
|
||||
|
||||
@@ -20,10 +19,13 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
{
|
||||
public event AuthCredentialsEventHandler AuthCredentialsRequired;
|
||||
public event BeforeBrowseEventHandler BeforeBrowse;
|
||||
public event BeforeContextMenuEventHandler BeforeContextMenu;
|
||||
public event BeforeDownloadEventHandler BeforeDownload;
|
||||
public event BeforeUnloadDialogEventHandler BeforeUnloadDialog;
|
||||
public event CanDownloadEventHandler CanDownload;
|
||||
public event ContextCreatedEventHandler ContextCreated;
|
||||
public event ContextMenuCommandEventHandler ContextMenuCommand;
|
||||
public event ContextMenuDismissedEventHandler ContextMenuDismissed;
|
||||
public event ContextReleasedEventHandler ContextReleased;
|
||||
public event DialogClosedEventHandler DialogClosed;
|
||||
public event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
@@ -40,6 +42,7 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
public event PreKeyEventHandler PreKeyEvent;
|
||||
public event ResetDialogStateEventHandler ResetDialogState;
|
||||
public event ResourceRequestEventHandler ResourceRequestHandlerRequired;
|
||||
public event RunContextMenuEventHandler RunContextMenu;
|
||||
public event SetFocusEventHandler SetFocus;
|
||||
public event TakeFocusEventHandler TakeFocus;
|
||||
public event UncaughtExceptionEventHandler UncaughtExceptionEvent;
|
||||
@@ -54,7 +57,7 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
JsDialogHandler = new JavaScriptDialogHandlerSwitch();
|
||||
KeyboardHandler = new KeyboardHandlerSwitch();
|
||||
LifeSpanHandler = lifeSpanHandler;
|
||||
MenuHandler = new ContextMenuHandler();
|
||||
MenuHandler = new ContextMenuHandlerSwitch();
|
||||
RenderProcessMessageHandler = new RenderProcessMessageHandlerSwitch();
|
||||
RequestHandler = new RequestHandlerSwitch();
|
||||
}
|
||||
@@ -79,6 +82,11 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
BeforeBrowse?.Invoke(webBrowser, browser, frame, request, userGesture, isRedirect, args);
|
||||
}
|
||||
|
||||
public void OnBeforeContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
|
||||
{
|
||||
BeforeContextMenu?.Invoke(webBrowser, browser, frame, parameters, model);
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback, GenericEventArgs args)
|
||||
{
|
||||
BeforeDownload?.Invoke(webBrowser, browser, downloadItem, callback, args);
|
||||
@@ -99,6 +107,16 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
ContextCreated?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnContextMenuCommand(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags, GenericEventArgs args)
|
||||
{
|
||||
ContextMenuCommand?.Invoke(webBrowser, browser, frame, parameters, commandId, eventFlags, args);
|
||||
}
|
||||
|
||||
public void OnContextMenuDismissed(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextMenuDismissed?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextReleased?.Invoke(webBrowser, browser, frame);
|
||||
@@ -174,6 +192,11 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
ResetDialogState?.Invoke(webBrowser, browser);
|
||||
}
|
||||
|
||||
public void OnRunContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback, GenericEventArgs args)
|
||||
{
|
||||
RunContextMenu?.Invoke(webBrowser, browser, frame, parameters, model, callback, args);
|
||||
}
|
||||
|
||||
public void OnSetFocus(IWebBrowser webBrowser, IBrowser browser, CefFocusSource source, GenericEventArgs args)
|
||||
{
|
||||
SetFocus?.Invoke(webBrowser, browser, source, args);
|
||||
|
@@ -18,10 +18,13 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
{
|
||||
public event AuthCredentialsEventHandler AuthCredentialsRequired;
|
||||
public event BeforeBrowseEventHandler BeforeBrowse;
|
||||
public event BeforeContextMenuEventHandler BeforeContextMenu;
|
||||
public event BeforeDownloadEventHandler BeforeDownload;
|
||||
public event BeforeUnloadDialogEventHandler BeforeUnloadDialog;
|
||||
public event CanDownloadEventHandler CanDownload;
|
||||
public event ContextCreatedEventHandler ContextCreated;
|
||||
public event ContextMenuCommandEventHandler ContextMenuCommand;
|
||||
public event ContextMenuDismissedEventHandler ContextMenuDismissed;
|
||||
public event ContextReleasedEventHandler ContextReleased;
|
||||
public event DialogClosedEventHandler DialogClosed;
|
||||
public event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
@@ -38,6 +41,7 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
public event PreKeyEventHandler PreKeyEvent;
|
||||
public event ResetDialogStateEventHandler ResetDialogState;
|
||||
public event ResourceRequestEventHandler ResourceRequestHandlerRequired;
|
||||
public event RunContextMenuEventHandler RunContextMenu;
|
||||
public event SetFocusEventHandler SetFocus;
|
||||
public event TakeFocusEventHandler TakeFocus;
|
||||
public event UncaughtExceptionEventHandler UncaughtExceptionEvent;
|
||||
@@ -70,6 +74,11 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
BeforeBrowse?.Invoke(webBrowser, browser, frame, request, userGesture, isRedirect, args);
|
||||
}
|
||||
|
||||
public void OnBeforeContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
|
||||
{
|
||||
BeforeContextMenu?.Invoke(webBrowser, browser, frame, parameters, model);
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback, GenericEventArgs args)
|
||||
{
|
||||
BeforeDownload?.Invoke(webBrowser, browser, downloadItem, callback, args);
|
||||
@@ -90,6 +99,16 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
ContextCreated?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnContextMenuCommand(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags, GenericEventArgs args)
|
||||
{
|
||||
ContextMenuCommand?.Invoke(webBrowser, browser, frame, parameters, commandId, eventFlags, args);
|
||||
}
|
||||
|
||||
public void OnContextMenuDismissed(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextMenuDismissed?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextReleased?.Invoke(webBrowser, browser, frame);
|
||||
@@ -165,6 +184,11 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
ResetDialogState?.Invoke(webBrowser, browser);
|
||||
}
|
||||
|
||||
public void OnRunContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback, GenericEventArgs args)
|
||||
{
|
||||
RunContextMenu?.Invoke(webBrowser, browser, frame, parameters, model, callback, args);
|
||||
}
|
||||
|
||||
public void OnSetFocus(IWebBrowser webBrowser, IBrowser browser, CefFocusSource source, GenericEventArgs args)
|
||||
{
|
||||
SetFocus?.Invoke(webBrowser, browser, source, args);
|
||||
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void BeforeContextMenuEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void ContextMenuCommandEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags, GenericEventArgs args);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void ContextMenuDismissedEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void RunContextMenuEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback, GenericEventArgs args);
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Handlers
|
||||
{
|
||||
internal class ContextMenuHandlerSwitch : IContextMenuHandler
|
||||
{
|
||||
public void OnBeforeContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
|
||||
{
|
||||
var control = default(ICefSharpControl);
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
}
|
||||
|
||||
control?.OnBeforeContextMenu(webBrowser, browser, frame, parameters, model);
|
||||
}
|
||||
|
||||
public bool OnContextMenuCommand(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
|
||||
{
|
||||
var args = new GenericEventArgs();
|
||||
var control = default(ICefSharpControl);
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
}
|
||||
|
||||
control?.OnContextMenuCommand(webBrowser, browser, frame, parameters, commandId, eventFlags, args);
|
||||
|
||||
return args.Value;
|
||||
}
|
||||
|
||||
public void OnContextMenuDismissed(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
var control = default(ICefSharpControl);
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
}
|
||||
|
||||
control?.OnContextMenuDismissed(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public bool RunContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
|
||||
{
|
||||
var args = new GenericEventArgs();
|
||||
var control = default(ICefSharpControl);
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
}
|
||||
|
||||
control?.OnRunContextMenu(webBrowser, browser, frame, parameters, model, callback, args);
|
||||
|
||||
return args.Value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,10 +21,13 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
{
|
||||
event AuthCredentialsEventHandler AuthCredentialsRequired;
|
||||
event BeforeBrowseEventHandler BeforeBrowse;
|
||||
event BeforeContextMenuEventHandler BeforeContextMenu;
|
||||
event BeforeDownloadEventHandler BeforeDownload;
|
||||
event BeforeUnloadDialogEventHandler BeforeUnloadDialog;
|
||||
event CanDownloadEventHandler CanDownload;
|
||||
event ContextCreatedEventHandler ContextCreated;
|
||||
event ContextMenuCommandEventHandler ContextMenuCommand;
|
||||
event ContextMenuDismissedEventHandler ContextMenuDismissed;
|
||||
event ContextReleasedEventHandler ContextReleased;
|
||||
event DialogClosedEventHandler DialogClosed;
|
||||
event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
@@ -41,6 +44,7 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
event PreKeyEventHandler PreKeyEvent;
|
||||
event ResetDialogStateEventHandler ResetDialogState;
|
||||
event ResourceRequestEventHandler ResourceRequestHandlerRequired;
|
||||
event RunContextMenuEventHandler RunContextMenu;
|
||||
event SetFocusEventHandler SetFocus;
|
||||
event TakeFocusEventHandler TakeFocus;
|
||||
event UncaughtExceptionEventHandler UncaughtExceptionEvent;
|
||||
@@ -50,10 +54,13 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
void GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling, ResourceRequestEventArgs args);
|
||||
void Load(string address);
|
||||
void OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect, GenericEventArgs args);
|
||||
void OnBeforeContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model);
|
||||
void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback, GenericEventArgs args);
|
||||
void OnBeforeUnloadDialog(IWebBrowser webBrowser, IBrowser browser, string message, bool isReload, IJsDialogCallback callback, GenericEventArgs args);
|
||||
void OnCanDownload(IWebBrowser webBrowser, IBrowser browser, string url, string requestMethod, GenericEventArgs args);
|
||||
void OnContextCreated(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
|
||||
void OnContextMenuCommand(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags, GenericEventArgs args);
|
||||
void OnContextMenuDismissed(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame);
|
||||
void OnContextReleased(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
|
||||
void OnDialogClosed(IWebBrowser webBrowser, IBrowser browser);
|
||||
void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback);
|
||||
@@ -69,6 +76,7 @@ namespace SafeExamBrowser.Browser.Wrapper
|
||||
void OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture, GenericEventArgs args);
|
||||
void OnPreKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut, GenericEventArgs args);
|
||||
void OnResetDialogState(IWebBrowser webBrowser, IBrowser browser);
|
||||
void OnRunContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback, GenericEventArgs args);
|
||||
void OnSetFocus(IWebBrowser webBrowser, IBrowser browser, CefFocusSource source, GenericEventArgs args);
|
||||
void OnTakeFocus(IWebBrowser webBrowser, IBrowser browser, bool next);
|
||||
void OnUncaughtException(IWebBrowser webBrowser, IBrowser browser, IFrame frame, JavascriptException exception);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CefSharp.Common" version="131.3.50" targetFramework="net48" />
|
||||
<package id="CefSharp.WinForms" version="131.3.50" targetFramework="net48" />
|
||||
<package id="chromiumembeddedframework.runtime.win-x64" version="131.3.5" targetFramework="net48" />
|
||||
<package id="chromiumembeddedframework.runtime.win-x86" version="131.3.5" targetFramework="net48" />
|
||||
<package id="CefSharp.Common" version="139.0.280" targetFramework="net48" />
|
||||
<package id="CefSharp.WinForms" version="139.0.280" targetFramework="net48" />
|
||||
<package id="chromiumembeddedframework.runtime.win-x64" version="139.0.28" targetFramework="net48" />
|
||||
<package id="chromiumembeddedframework.runtime.win-x86" version="139.0.28" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||
<package id="Syroot.Windows.IO.KnownFolders" version="1.3.0" targetFramework="net48" />
|
||||
<package id="System.Security.Principal.Windows" version="5.0.0" targetFramework="net48" />
|
||||
|
Reference in New Issue
Block a user