For a project I am working on I need to inject javascript prior to any of the webpage document processing begins. This can easily be achieved via the WebBrowser ponent, but I am encountering difficulty using CefSharp.
Here is a simplification of the problem, a webpage needs an "InjectedObject" to be present to function. Calling the webpage without injection occurring at the very top of the document, or being evaluated/executed before the document is processed would result in:
=====html example output on failure=====
isObjectPresent?
false
=====
Where as I need the webpage to display:
=====html example output on success=====
isObjectPresent?
true
=====
<html>
<head>
<script>
isObjectPresent = typeof InjectedObject == "object";
</script>
</head>
<body>
<p>isObjectPresent?</p>
<div id="result"></div>
<script>
document.getElementById("result").innerHTML = isObjectPresent;
</script>
</body>
</html>
Looking at all the available suggestions would indicate I should use LoadingStateChanged() or FrameLoadEnd() to inject the script, ie:
public void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs args) {
if (args.Frame.IsMain) {
args.Frame.ExecuteJavascriptAsync("window.InjectedObject = {};");
}
}
However all iterations I have tried of this, and even using FrameLoadStart, has resulted in the inserted javascript occurring after the document has begun processing. Is there any example of a true javascript injection insuring it occurs BEFORE document processing begins. (making sure to avoid a race condition/timing issue).
As an example of the WebBrowser ponent behavior that I am looking to imitate is:
private void uiWebBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
var browser = (WebBrowser)sender;
var document = browser.Document as HTMLDocument;
var head = document.getElementsByTagName("head").Cast<HTMLHeadElement>().First();
if (head != null)
{
var script = document.createElement("script") as IHTMLScriptElement;
script.text = "window.InjectedObject = {};"
if (head.firstChild != null)
{
head.insertBefore((IHTMLDOMNode)script, head.firstChild);
}
else
{
head.appendChild((IHTMLDOMNode)script;
}
}
}
Any help or suggestion is wele, ideally I'd like to avoid downloading the page via an internet request parsing and inserting, and then using loadhtml, since I expect I would have to do that potentially for All navigation actions that impacted the main frame, which sounds like a hack job.
Following up from the ments it was suggested that the javascript V8 engine context was sufficient for the above use case. Attempting to implement the OnContextCreated method from the IRenderProcessMessageHandler interface has the same results.
==MainWindow.xaml==
<Window x:Class="ExampleCefSharp001.MainWindow"
xmlns=""
xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
xmlns:x=""
xmlns:d=""
xmlns:mc=""
xmlns:local="clr-namespace:ExampleCefSharp001"
mc:Ignorable="d"
Title="MainWindow" Height="1000" Width="1100">
<Grid>
<cefSharp:ChromiumWebBrowser x:Name="uiWebView"></cefSharp:ChromiumWebBrowser>
</Grid>
</Window>
==MainWindow.xaml.cs==
public partial class MainWindow : Window
{
JavascriptManager jsmanager;
public MainWindow()
{
InitializeComponent();
jsmanager = new JavascriptManager(uiWebView);
}
}
public class JavascriptManager : ILoadHandler, IRenderProcessMessageHandler
{
string injection = "window.InjectedObject = {};";
public JavascriptManager(ChromiumWebBrowser browser)
{
browser.LoadHandler = this;
browser.RenderProcessMessageHandler = this;
// Lets just pretend this is a real url with the example html above.
browser.Address = ".htm"
}
public void OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
{
frame.ExecuteJavaScriptAsync(injection);
}
}
I do appreciate the ments and suggestions. If there is something I am missing please let me know!
For a project I am working on I need to inject javascript prior to any of the webpage document processing begins. This can easily be achieved via the WebBrowser ponent, but I am encountering difficulty using CefSharp.
Here is a simplification of the problem, a webpage needs an "InjectedObject" to be present to function. Calling the webpage without injection occurring at the very top of the document, or being evaluated/executed before the document is processed would result in:
=====html example output on failure=====
isObjectPresent?
false
=====
Where as I need the webpage to display:
=====html example output on success=====
isObjectPresent?
true
=====
<html>
<head>
<script>
isObjectPresent = typeof InjectedObject == "object";
</script>
</head>
<body>
<p>isObjectPresent?</p>
<div id="result"></div>
<script>
document.getElementById("result").innerHTML = isObjectPresent;
</script>
</body>
</html>
Looking at all the available suggestions would indicate I should use LoadingStateChanged() or FrameLoadEnd() to inject the script, ie:
public void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs args) {
if (args.Frame.IsMain) {
args.Frame.ExecuteJavascriptAsync("window.InjectedObject = {};");
}
}
However all iterations I have tried of this, and even using FrameLoadStart, has resulted in the inserted javascript occurring after the document has begun processing. Is there any example of a true javascript injection insuring it occurs BEFORE document processing begins. (making sure to avoid a race condition/timing issue).
As an example of the WebBrowser ponent behavior that I am looking to imitate is:
private void uiWebBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
var browser = (WebBrowser)sender;
var document = browser.Document as HTMLDocument;
var head = document.getElementsByTagName("head").Cast<HTMLHeadElement>().First();
if (head != null)
{
var script = document.createElement("script") as IHTMLScriptElement;
script.text = "window.InjectedObject = {};"
if (head.firstChild != null)
{
head.insertBefore((IHTMLDOMNode)script, head.firstChild);
}
else
{
head.appendChild((IHTMLDOMNode)script;
}
}
}
Any help or suggestion is wele, ideally I'd like to avoid downloading the page via an internet request parsing and inserting, and then using loadhtml, since I expect I would have to do that potentially for All navigation actions that impacted the main frame, which sounds like a hack job.
Following up from the ments it was suggested that the javascript V8 engine context was sufficient for the above use case. Attempting to implement the OnContextCreated method from the IRenderProcessMessageHandler interface has the same results.
==MainWindow.xaml==
<Window x:Class="ExampleCefSharp001.MainWindow"
xmlns="http://schemas.microsoft./winfx/2006/xaml/presentation"
xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
xmlns:x="http://schemas.microsoft./winfx/2006/xaml"
xmlns:d="http://schemas.microsoft./expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats/markup-patibility/2006"
xmlns:local="clr-namespace:ExampleCefSharp001"
mc:Ignorable="d"
Title="MainWindow" Height="1000" Width="1100">
<Grid>
<cefSharp:ChromiumWebBrowser x:Name="uiWebView"></cefSharp:ChromiumWebBrowser>
</Grid>
</Window>
==MainWindow.xaml.cs==
public partial class MainWindow : Window
{
JavascriptManager jsmanager;
public MainWindow()
{
InitializeComponent();
jsmanager = new JavascriptManager(uiWebView);
}
}
public class JavascriptManager : ILoadHandler, IRenderProcessMessageHandler
{
string injection = "window.InjectedObject = {};";
public JavascriptManager(ChromiumWebBrowser browser)
{
browser.LoadHandler = this;
browser.RenderProcessMessageHandler = this;
// Lets just pretend this is a real url with the example html above.
browser.Address = "https://www.example./timingtest.htm"
}
public void OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
{
frame.ExecuteJavaScriptAsync(injection);
}
}
I do appreciate the ments and suggestions. If there is something I am missing please let me know!
Share Improve this question edited Jan 24, 2017 at 21:02 Glorifundel asked Jan 19, 2017 at 15:36 GlorifundelGlorifundel 8231 gold badge10 silver badges17 bronze badges 8-
Read the
FAQ
, the topic is covered there. – amaitland Commented Jan 19, 2017 at 21:01 - Hi Amaitland, I assume you are speaking of [github./cefsharp/CefSharp/wiki/Frequently-asked-questions], which indicates numerous ways to inject javascript at run time, but makes no mention of preprocessing (that I can see). If there is a reference to preprocessing I would be very interested to know where it is. – Glorifundel Commented Jan 20, 2017 at 14:20
- V8 context creation is very likely sufficient for your purposes. – amaitland Commented Jan 20, 2017 at 19:17
- Updated with code using OnContextCreated, unfortunately had the same results. – Glorifundel Commented Jan 24, 2017 at 21:03
- hi amaitland, I did update the question specifically based on your suggestion to use the V8 context creation, which I assume you mean was to use OnContextCreated. I even added the MainWindow.xaml (not that I was doing much with it) as well as the MainWindow.xaml.cs code for a more plete view. the html page itself is the test. if there is not an object assigned to window.InjectedObject it simply displays on the body that it was not present. The problem is preprocessing, in other browsers I can inject javascript ahead of all other javascript on the html page. – Glorifundel Commented Jan 24, 2017 at 22:25
2 Answers
Reset to default 7Finally got back to this. Heavily based on example found in: CefSharp.Example/Filters/FindReplaceResponseFilter.cs
implementing the IRequestHandler and IResponseFilter interfaces:
==MainWindow.xaml==
<Window x:Class="ExampleCefSharp001.MainWindow"
xmlns="http://schemas.microsoft./winfx/2006/xaml/presentation"
xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
xmlns:x="http://schemas.microsoft./winfx/2006/xaml"
xmlns:d="http://schemas.microsoft./expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats/markup-patibility/2006"
xmlns:local="clr-namespace:ExampleCefSharp001"
mc:Ignorable="d"
Title="MainWindow" Height="1000" Width="1100">
<Grid>
<cefSharp:ChromiumWebBrowser x:Name="uiWebView"></cefSharp:ChromiumWebBrowser>
</Grid>
</Window>
==MainWindow.xaml.cs==
public partial class MainWindow : Window
{
JavascriptManager jsmanager;
public MainWindow()
{
InitializeComponent();
jsmanager = new JavascriptManager(uiWebView);
}
}
public class JavascriptManager : IRequestHandler
{
string injection = "window.InjectedObject = {};";
public JavascriptManager(ChromiumWebBrowser browser)
{
browser.RequestHandler = this;
// Lets just pretend this is a real url with the example html above.
browser.Address = "https://www.example./timingtest.htm"
}
public IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{
if (frame.IsMain && request.ResourceType == ResourceType.MainFrame)
{
return new JavascriptInjectionFilter(injection);
}
return null;
}
}
public class JavascriptInjectionFilter : IResponseFilter
{
/// <summary>
/// Location to insert the javascript
/// </summary>
public enum Locations
{
/// <summary>
/// Insert Javascript at the top of the header element
/// </summary>
head,
/// <summary>
/// Insert Javascript at the top of the body element
/// </summary>
body
}
string injection;
string location;
int offset = 0;
List<byte> overflow = new List<byte>();
/// <summary>
/// Constructor
/// </summary>
/// <param name="injection"></param>
/// <param name="location"></param>
public JavascriptInjectionFilter(string injection, Locations location = Locations.head)
{
this.injection = "<script>" + injection + "</script>";
switch (location)
{
case Locations.head:
this.location = "<head>";
break;
case Locations.body:
this.location = "<body>";
break;
default:
this.location = "<head>";
break;
}
}
/// <summary>
/// Disposal
/// </summary>
public void Dispose()
{
//
}
/// <summary>
/// Filter Processing... handles the injection
/// </summary>
/// <param name="dataIn"></param>
/// <param name="dataInRead"></param>
/// <param name="dataOut"></param>
/// <param name="dataOutWritten"></param>
/// <returns></returns>
public FilterStatus Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten)
{
dataInRead = dataIn == null ? 0 : dataIn.Length;
dataOutWritten = 0;
if (overflow.Count > 0)
{
var buffersize = Math.Min(overflow.Count, (int)dataOut.Length);
dataOut.Write(overflow.ToArray(), 0, buffersize);
dataOutWritten += buffersize;
if (buffersize < overflow.Count)
{
overflow.RemoveRange(0, buffersize - 1);
}
else
{
overflow.Clear();
}
}
for (var i = 0; i < dataInRead; ++i)
{
var readbyte = (byte)dataIn.ReadByte();
var readchar = Convert.ToChar(readbyte);
var buffersize = dataOut.Length - dataOutWritten;
if (buffersize > 0)
{
dataOut.WriteByte(readbyte);
dataOutWritten++;
}
else
{
overflow.Add(readbyte);
}
if (char.ToLower(readchar) == location[offset])
{
offset++;
if (offset >= location.Length)
{
offset = 0;
buffersize = Math.Min(injection.Length, dataOut.Length - dataOutWritten);
if (buffersize > 0)
{
var data = Encoding.UTF8.GetBytes(injection);
dataOut.Write(data, 0, (int)buffersize);
dataOutWritten += buffersize;
}
if (buffersize < injection.Length)
{
var remaining = injection.Substring((int)buffersize, (int)(injection.Length - buffersize));
overflow.AddRange(Encoding.UTF8.GetBytes(remaining));
}
}
}
else
{
offset = 0;
}
}
if (overflow.Count > 0 || offset > 0)
{
return FilterStatus.NeedMoreData;
}
return FilterStatus.Done;
}
/// <summary>
/// Initialization
/// </summary>
/// <returns></returns>
public bool InitFilter()
{
return true;
}
}
Thanks to amaitland for pointing me in the right direction, and for the sample program that the vast majority of the above code was based on. End result:
<html><head></head><body><script>window.InjectedObject = {}</script>
<script>
isObjectPresent = typeof InjectedObject == "object";
</script>
<p>isObjectPresent?</p>
<div id="result"></div>
<script>
document.getElementById("result").innerHTML = isObjectPresent;
</script>
</body></html>
Which meets my needs of pre-processing the document with some text at the top of the header ensuring no timing issues where existing code might be run before the injected code.
edit couple small fixes. added control logic to only insert when a mainframe is loaded.
Well your answer is correct that you should override the implementation of GetResourceResponseFilter
but in case you didn't implement the interface in a correct way you will end up with browser not rendering content, you can instead inherit the DefaultRequestHandler
and override the GetResourceResponseFilter()
and provide the custom filter as mentioned in the accepted answer, this will be easier in case you need only to ovveride this specific functionality :
public class CustomRequestHandler : DefaultRequestHandler
{
string script = "alert('hello');";
public override IResponseFilter GetResourceResponseFilter(CefSharp.IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{
if (frame.IsMain && request.ResourceType == ResourceType.MainFrame)
{
return new JavascriptInjectionFilter(script);
}
return null;
}
}
Then assign it to the chromium browser:
CustomRequestHandler customRequestHandler = new CustomRequestHandler();
chromeBrowser.RequestHandler = customRequestHandler ;