CefSharp: Injecting custom CSS File using a custom scheme - winforms

I'm using CefSharp (47) to render a webpage from a host that I have no control over, and I want to make some additional CSS tweaks to those provided by the host.
Reading up on various topics across GitHub (https://github.com/cefsharp/CefSharp/blob/cefsharp/47/CefSharp.Example/CefSharpSchemeHandlerFactory.cs), and here (CefSharp custom SchemeHandler), I wrote a custom scheme handler accordingly:
public class CustomSchemeHandlerFactory : ISchemeHandlerFactory
{
public const string SchemeName = "custom";
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{
Console.WriteLine(request.Url);
if (schemeName.ToLower() == SchemeName.ToLower())
{
// Do some stuff
}
return null;
}
}
I attempt to bind it in my application in the following manner:
CefSettings settings = new CefSettings();
settings.CachePath = browserCachePath;
settings.RegisterScheme(new CefCustomScheme()
{
SchemeName = CustomSchemeHandlerFactory.SchemeName,
SchemeHandlerFactory = new CustomSchemeHandlerFactory()
});
Cef.Initialize(settings);
The application then browses to the appropriate website, and uses the 'LoadingStateChanged' event to then fire off some JavaScript to inject the CSS file I want to load:
string linkText = "<link rel=\u0022stylesheet\u0022 type=\u0022text/css\u0022 href=\u0022custom://custom.css\u0022>";
var jsFunctionText = string.Format("(function() {{ $('head').append('{0}'); return true;}}) ();", linkText);
var injectionTask = await _myBrowser.GetMainFrame().EvaluateScriptAsync(jsFunctionText, null);
...which succeeds.
But my custom resource handler 'Create' event is never fired.
I can only presume that the handler isn't being registered properly, so I'd appreciate any advice/help in getting this working properly!
Thanks!

Related

Is it possible to send a message from a WPF app that is hosting a CefSharp control, to the web app running in CefSharp like WebView2 can do

With WebVeiw2 you can send a message to a web app running in it using WebView2Ctrl?.CoreWebView2?.PostWebMessageAsJson(message).
Is there a way of doing this in CefSharp
Create a class (I used JavascriptCallbackMessenger) to Set and Run the callbacks.
public class JavascriptCallbackMessenger
{
private IJavascriptCallback _callback;
public void SetCallBack(IJavascriptCallback callback)
{
_callback = callback;
}
public void RunCallback(string message)
{
if (_callback != null && _callback.CanExecute)
{
_callback.ExecuteAsync(message);
}
}
}
Create an instance of JavascriptCallbackMessenger and register it with the CefSharp control
CefSharpCtrl.JavascriptObjectRepository.Register(JavascriptCallbackMessengerName, _messenger, true, BindingOptions.DefaultBinder);
Set the callback in Javascript as follows (I'm not a JS developer, but this was my solution).
(async function() {
const cefSharp = (window as any).CefSharp;
await cefSharp.BindObjectAsync(JavascriptCallbackMessengerName);
window.javascriptCallbackMessenger.setCallBack(function(message: string)
{
console.log("messageHandler: " + message);
})
})();
I was using typescript, so I had to extend the Window with the newly created variable.
declare global {
interface Window { javascriptCallbackMessenger: any; }
}
Apologies, but the formatting seems to be a bit "off"!

How can I block loading remote content in DotNetBrowser?

I'm using the DotNetBrowser in my WPF emailing application to display the emails content. I'd like to block every remote content and remote images. I use this email privacy tester to check if I can correctly block the remote content.
I checked the documentation of the DotNetBrowser and I found that it's possible to disable couple of things using the BrowserPreferences setting. I disabled everything with the following piece of code:
BrowserPreferences pref = new BrowserPreferences {
ImagesEnabled = false,
JavaScriptEnabled = false,
PluginsEnabled = false,
WebAudioEnabled = false,
ApplicationCacheEnabled = false,
LocalStorageEnabled = false,
AllowDisplayingInsecureContent = false,
AllowRunningInsecureContent = false,
...
};
wpfBrowserView.Browser.Preferences = pref;
wpfBrowserView.Browser.LoadHTML(myHtml);
But this blocks just couple of the possible harmful contents. Then I set a custom LoadHandler, where I could prevent couple of more cases:
MyBrowserLoadHandler loadHandler = new MyBrowserLoadHandler();
loadHandler.Load += args => {
// just don't allow to load the content
};
wpfBrowserView.Browser.LoadHandler = loadHandler;
This is not enough, because it still fails two of them (the Link Prefetch and the CSS link tag).
I don't want to do a static analysis on the email's html to handle those cases as well, so I'm searching for an easier way to do it. For example in the Android's WebView, it's just two methods to call (setBlockNetworkLoads(true) and setBlockNetworkImage(true)) and it does the whole thing. Is there a solution like this in DotNetBrowser?
Finally I found the solution. I dropped out all of my tries what I posted above, and tried another way. The DotNetBrowser has a resource handler and you can set what kind of resources you'd like to allow to load and what not. Here is my code:
var network = wpfBrowserView.Browser.Context.NetworkService;
var resourceHandler = new MyResourceHandler();
resourceHandler.Load += args => {
ResourceType resource = args.Parameters.ResourceType;
switch (resource) {
case ResourceType.PREFETCH:
case ResourceType.IMAGE:
case ResourceType.MEDIA:
case ResourceType.OBJECT:
case ResourceType.STYLESHEET:
case ResourceType.FONT_RESOURCE:
case ResourceType.SUB_RESOURCE:
return false;
default:
// allow to load for the others
return true;
}
};
network.ResourceHandler = resourceHandler;
And the custom resource handler:
public class ResourceLoadEventArgs {
public ResourceParams Parameters { get; set; }
}
public delegate bool ResourceLoadHandler(ResourceLoadEventArgs args);
public class MyResourceHandler : ResourceHandler {
public event ResourceLoadHandler Load;
public bool CanLoadResource(ResourceParams parameters) {
return Load?.Invoke(new ResourceLoadEventArgs { Parameters = parameters }) ?? true;
}
}
So, adding this piece of code before loading the html into the browser view, it causes to pass every test in the email privacy tester. Then you can put a button for the user to load the remote content, and when the user clicks on it, you can allow every resource.

Cannot get an IdentityServer4 custom cookie handler working

I'm unable to get a custom cookie authentication handler working with IdentityServer4. I'm using ASP.NET Core Identity and have followed the official guide: https://identityserver4.readthedocs.io/en/release/topics/signin.html
I need to override the CookieAuthenticationEvents.ValidatePrincipal and CookieAuthenticationEvents.SignedIn event handlers.
I've written a class that inherits CookieAuthenticationEvents and overrides the two event handlers.
I'm assigning it to a custom cookie handler via:
var auth = services.AddAuthentication("MyCookies");
auth.AddCookie("MyCookies", options =>
{
options.Events = new RealtimeStatusCookieAuthEvents(Configuration);
});
Here's my code:
https://gist.github.com/Amethi/f3411038a9447d274c0b721698fc5e63
The event handlers don't fire, i.e. I'm expecting them to fire for each request (due to ValidatePrincipal) and when I come back to the site after closing the browser and sign-in using cookie authentication (SignedIn).
Anyone know what I'm doing wrong?
Update:
Even simplifying it as follows doesn't help. The event handlers don't fire.
var auth = services.AddAuthentication("CustomCookies").AddCookie("CustomCookies", options =>
{
options.Events = new CookieAuthenticationEvents
{
OnSignedIn = context =>
{
Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
"OnSignedIn", context.Principal.Identity.Name);
return Task.CompletedTask;
},
OnValidatePrincipal = context =>
{
Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
"OnValidatePrincipal", context.Principal.Identity.Name);
return Task.CompletedTask;
},
};
});
I managed to make my custom cookie authentication handler work by using the ConfigureApplicationCookie extension.
builder.Services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "IdentityServer.Cookie";
config.EventsType = typeof(CustomCookieAuthenticationHandler);
config.LoginPath = "/Account/Login";
});
And register the CustomCookieAuthenticationHandler handler
builder.Services.AddScoped<CustomCookieAuthenticationHandler>();
This is the handler implementation:
public class CustomCookieAuthenticationHandler: CookieAuthenticationEvents
{
private readonly IUserRepository _userRepository;
public CustomCookieAuthenticationEvents(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public override Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
// Your cookie authentication logic.
}
}
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0

Getting the current page when receiving a toast notification (WP8.1 Silverlight, receiving WNS toast notification)

I have an event that fires when the app is live and I receive an notification CurrentChannel_PushNotificationReceived. In this function I want to find out which page is currently displayed to know if the notification should update content on the page. The question is therefore twofold, how to know which page is currently displayed and interact with the toast notification.
Update
The issue is that I cannot interact with the elements because of clash with the OS threading (Dispatcher).
Therefore using the below code it allows me to access the content of the message. But I am still not able to get the info of the current_page
_channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
_channel.PushNotificationReceived += OnPushNotificationReceived;
private void OnPushNotificationReceived(PushNotificationChannel sender, PushNotificationReceivedEventArgs args)
{
switch (args.NotificationType)
{
case PushNotificationType.Badge:
this.OnBadgeNotificationReceived(args.BadgeNotification.Content.GetXml());
break;
case PushNotificationType.Tile:
this.OnTileNotificationReceived(args.TileNotification.Content.GetXml());
break;
case PushNotificationType.Toast:
this.OnToastNotificationReceived(args.ToastNotification.Content.GetXml());
break;
case PushNotificationType.Raw:
this.OnRawNotificationReceived(args.RawNotification.Content);
break;
}
args.Cancel = true;
}
private void OnBadgeNotificationReceived(string notificationContent)
{
// Code when a badge notification is received when app is running
}
private void OnTileNotificationReceived(string notificationContent)
{
// Code when a tile notification is received when app is running
}
private void OnToastNotificationReceived(string notificationContent)
{
// Code when a toast notification is received when app is running
// Show a toast notification programatically
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(notificationContent);
var toastNotification = new ToastNotification(xmlDocument);
//toastNotification.SuppressPopup = true;
ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
}
private void OnRawNotificationReceived(string notificationContent)
{
// Code when a raw notification is received when app is running
}
Question
How do I access the current page information in the different onXXXXNotificationReceived. The current snippets work but not within these functions:
var currentPage = ((PhoneApplicationFrame)Application.Current.RootVisual).Content;
var tempBool = currentPage.GetType() is BC_Menu.StartUp.SecondScreen;
or
RootFrame.CurrentSource;
My guess is it is because of the UI-thread. So how can I use the dispatcher to get the information? I have tried some solutions with the dispatcher, but I cannot await the information, and therefore it is not applicable.
System.Windows.Threading.DispatcherOperation op = App.RootFrame.Dispatcher.BeginInvoke(new Func<Uri>(() =>
{
return RootFrame.CurrentSource;
})
);
await op; //Not awaitable.
There's no reason to await the dispatcher to the UI thread. Simply dispatch to the UI thread and then perform the rest of your logic, like displaying the toast or navigating to a page, from within the UI thread...
Register the event...
var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
channel.PushNotificationReceived += Channel_PushNotificationReceived;
On the event handler, cancel displaying the notification and then dispatch to UI thread...
private void Channel_PushNotificationReceived(PushNotificationChannel sender, PushNotificationReceivedEventArgs args)
{
// Make sure you cancel displaying the toast on this thread (not on UI thread)
// since cancellation needs to be set before this thread/method returns
args.Cancel = true;
// Then dispatch to the UI thread
App.RootFrame.Dispatcher.BeginInvoke(delegate
{
var currPage = ((PhoneApplicationFrame)Application.Current.RootVisual).Content;
switch (args.NotificationType)
{
case PushNotificationType.Toast:
// TODO
break;
}
});
}
Do all of your code inside the dispatcher's delegate. All your code will be executing on the UI thread... you'll be able to navigate pages, obtain current page, etc.
Ok. Try this. Create a static property on App.xaml.cs.
public static object CurrentPageInfo { get; set; }
And assign the page type or page name to the property on 'OnNavigatedTo' method on every page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var currentPage = ((PhoneApplicationFrame)Application.Current.RootVisual).Content;
App.CurrentPageInfo = currentPage.GetType() is BC_Menu.StartUp.SecondScreen;
}
So that you can identify the page source type on receiving notifications by accessing the App.CurrentPageInfo property. Hope it helps!

SignalR doesn't push message to client

I am implementing functionality to notify the user of long running job completions using SignalR in an AngularJS application.I have created groups of user based on their name,so for each user a group of his name and different connectionids which he has opened up will be created and he would be notified by his group. I want to notify the user on two pages i.e. landing Page and Job Run Page as even if the user is on landing page and job run completes he should be notified of it.
For the same reason i am creating group by his name on both the pages,so that if he is on any page he would be nofied through the group.
On landing page controller js file i have written code to add the user in group as follow...
$rootScope.signalRHub = $.connection.signalRHub;
$rootScope.hubStart = null;
$rootScope.startHub = function () {
if ($rootScope.hubStart == null)
{
$rootScope.hubStart = $.connection.hub.start();
}
return $rootScope.hubStart;
}
$scope.$on('$locationChangeStart', function (event) {
if ($rootScope.userName != "") {
$rootScope.signalRHub.server.leaveGroup($rootScope.userName);
}
});
// Start the connection
$rootScope.startHub().done(function () {
$rootScope.signalRHub.server.joinGroup($rootScope.userName);
});
on Job Run controller js file i have written following code....
$rootScope.signalRHub.client.showNotification = function (message) {
notify('Your notification message');//notify is the angular js directive injected in this controller which runs fine
};
$scope.$on('$locationChangeStart', function (event) {
$rootScope.signalRHub.server.leaveGroup($rootScope.studyid);
});
// Start the connection
$rootScope.startHub().done(function () {
$rootScope.signalRHub.server.joinGroup($rootScope.userName
});
My Hub File.....
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class SignalRHub : Hub
{
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public void ShowNotification(string jobRunDetailId, string userName)
{
if (!string.IsNullOrEmpty(userName))
{
var context = GlobalHost.ConnectionManager.GetHubContext<SignalRHub>();
context.Clients.Group(userName).showNotification(jobRunDetailId);
}
}
}
The issue is when i run the application the group add functionality for both pages works fine.but when i call "showNotification" from Hub it doesn't show any message.
But strange thing is if i comment the "$rootScope.startHub().done...." function on landing page then the jobrun page notify functionality works fine.I am not sure if writing "$rootScope.startHub().done()..." on two places is creating this problem.please help.
You need to wire up all callbacks before calling start. If you turn client side logging on, it'll tell you what hubs you are subscribed to.
Aside:
[EnableCors] is a webapi specific attribute that does not work in SignalR.

Resources