Open igotbass opened 4 years ago
There isn't a DOM (Document) implementation for WebView2, but I am working on one here.
There isn't a DOM (Document) implementation for WebView2, but I am working on one here.
There is no native way to access DOM objects from Winforms app ? How is that even possible ?
Create a .Net DOM framework and update it from JS.
Create a .Net DOM framework and update it from JS.
The documentation does not explain how to get values out of the control into .net. I mean this is the most basic functionality of a webbrowser control ? How is this not explained in depth in the documentation?
It is far more flexible and cross browser/platform and more secure to do it in JS. It is explained how to communicate between .Net and JS, it's up to us what we do with it.
Hi @igotbass, thanks for these great questions! Looks like we need some documentation regarding how to interact with the DOM.
@igotbass, Regarding interacting with the DOM, yes what @ukandrewc is true. You can use ExecuteScript to execute script and you'll get back the result of that script as JSON. Or you can use PostWebMessageAsJson/chrome.webview.addEventListener('message',...) to send messages into the document and chrome.webview.postMessage/WebMessageReceived to send messages from the document back to the host app.
Regarding clearing cookies or other state, that's a great feature request which we don't support yet.
To get a fresh browser session, when you create the CoreWebView2Environment you specify the user data folder. The user data folder holds all state including cookies, http cache, and so on. If you delete the user data folder or specify a new user data folder you will get a fresh environment.
Hi @igotbass, thanks for these great questions! Looks like we need some documentation regarding how to interact with the DOM.
@igotbass, Regarding interacting with the DOM, yes what @ukandrewc is true. You can use ExecuteScript to execute script and you'll get back the result of that script as JSON. Or you can use PostWebMessageAsJson/chrome.webview.addEventListener('message',...) to send messages into the document and chrome.webview.postMessage/WebMessageReceived to send messages from the document back to the host app.
Regarding clearing cookies or other state, that's a great feature request which we don't support yet.
To get a fresh browser session, when you create the CoreWebView2Environment you specify the user data folder. The user data folder holds all state including cookies, http cache, and so on. If you delete the user data folder or specify a new user data folder you will get a fresh environment.
So if I want something as simple as the oldschool webbrowser.document.getelementbyid("xxx").value its now a massive process to get a element value ?
I suppose that's part of the point, this is newschool ;-) but if you use this version that implements a DOM.
It is still a work in progress, so don't expect too much, but you can access most of Window, Document, Element, Attribute, Style and Node.
I suppose that's part of the point, this is newschool ;-) but if you use this version that implements a DOM.
It is still a work in progress, so don't expect too much, but you can access most of Window, Document, Element, Attribute, Style and Node.
Ok thanks I will check it out.
I just tried @ukandrewc browser and this is the functionality that MUST be built into webview2 from MS, not exactly sure how a 3rd party has managed to put this together but we should not have to inject javascript to get a simple value of a element. Webbrowser control 1 line of code
string TheValue = webbrowser1.document.getelementbyid("xxx").value
Now its setting up and receiving JSON messages ect, guys this should be basic functionality of the control, hands down.
Thanks
not exactly sure how a 3rd party has managed to put this together
Ones and zeros, that's all there is to it. At least until we have quantum processing, then it will be ones and or possibly maybe zeros (ah now you've looked at it, so we'll have to start again ;-)
100% agree that having DOM access without having to do a bunch of Javascript injecting is the right way to do it. I hope they incorporate what @ukandrewc has created.
I need to have the touch keyboard popping up when a user tap (or mouse click) an input field. This is automatically performed when the computer has no physical keyboard, or when it's a slate configured in tablet mode. But since it's not always the case, i had to do the following with my former WebBrowser control :
protected override void WndProc(ref Message m)
{
const int WM_PARENTNOTIFY = 0x0210,
WM_POINTERDOWN = 0x0246;
if (KioskConfig.TouchEnabledPc && m.Msg == WM_PARENTNOTIFY && Loword(m.WParam) == WM_POINTERDOWN)
{
var x = Loword(m.LParam);
var y = Hiword(m.LParam);
if (_wb.Document != null)
{
Point coords = new Point(x, y);
Point cliCoords = _wb.PointToClient(coords);
HtmlElement clickedElement = _wb.Document.GetElementFromPoint(cliCoords);
// Only on INPUT tags
if (clickedElement != null && clickedElement.TagName == "INPUT")
{ ....
And also hooking mouse clicks :
private void MouseEventProc(object sender, EventArgs e)
{
if (_wb.Document != null)
{
HtmlElement clickedElement = _wb.Document.GetElementFromPoint(_wb.PointToClient(Cursor.Position));
if (clickedElement != null && clickedElement.TagName == "INPUT")
{
Question is : since no easy DOM with WebView2, is there any way of getting the html tag at click/tab coords without having to exec some JS (could probably be very intensive at each click/tap) ?
This experimental DOM allows that: https://github.com/ukandrewc/Webview2.Winforms.DOM
There's no need for GetElementFromPoint
because in the DOMClickEvent, Document.ActiveElement
will give you any editable element.
@ukandrewc thank you ! I will check this out
I am in complete agreement with the person who initiated this thread. How can Microsoft release WebView2 without an equivalent function to "WebBrowser.Document"?? This is like GM producing a car without seats and expecting each consumer to implement their own custom seat solution.
Some of us are not expert Java Script developers. We all will waste a lot of time trying to reproduce the basic functionality we enjoyed in WebBrowser. I for one spent the last three days trying to find a solution with no luck. My searches on the internet show me that I am not alone. I would like to request that someone provide a clear and concise example that illustrates how we would duplicate the WebServer.Document functionality. This should include any needed Java Script code, as well as, code for decrypting the data using the new System.Text.JSON libraries.
I included my working source below. This is a wrapper class for interfacing with WebView2. I would appreciate any comments/suggestions you might have.
'Option Explicit On 'Option Strict On
Imports Microsoft.Web.WebView2.Core Imports Microsoft.Web.WebView2.WinForms
' This class serves as a wrapper for the Microsoft Edge browser. Public Class EdgeBrowser ' Constant variable definitions. Private Const HTTP As String = "http://" Private Const HTTPS As String = "https://"
' Private member data.
Private m_browser As WebView2 = Nothing
Private m_isDebugModeEnabled As Boolean = False
Private m_isInitialized As Boolean = False
Private m_isNavigating As Boolean = False
Private m_isScriptProcessing As Boolean = False
Private m_message As String = Nothing
' Public class methods.
' These methods provide access to class properties.
Public ReadOnly Property Browser() As WebView2
Get
Return m_browser
End Get
End Property
Public Property IsDebugModeEnabled() As Boolean
Get
Return m_isDebugModeEnabled
End Get
Set(value As Boolean)
m_isDebugModeEnabled = value
End Set
End Property
Public ReadOnly Property IsInitialized() As Boolean
Get
Return m_isInitialized
End Get
End Property
Public ReadOnly Property IsNavigating() As Boolean
Get
Return m_isNavigating
End Get
End Property
' This method navigates to the given URL and returns the results.
Public Function NavigateTo(urlAddress As String) As String
' Wait for the browser to finish processing, if needed.
Dim waitUntil As Date = Date.Now.AddSeconds(30)
While Date.Now < waitUntil
If m_isInitialized = True And m_isNavigating = False Then
Exit While
Else
Application.DoEvents()
End If
End While
' Ensure the browser is ready.
Dim msg As String = Nothing
If m_isInitialized = False Then
msg = "Unable to initialize the Microsoft Edge browser."
GSL.FileOperations.logWarning(msg)
Throw New Exception(msg)
ElseIf m_isNavigating = True Then
m_isNavigating = False
msg = "Error while navigating."
GSL.FileOperations.logWarning(msg)
Throw New Exception(msg)
Else
m_isNavigating = True
End If
' Navigate to the given address.
urlAddress = FixURLAddress(urlAddress)
Log("#New Navigation Request To : " & urlAddress)
m_browser.Source = New System.Uri(
urlAddress,
UriKind.Absolute
)
' Wait for the navigation to complete.
waitUntil = Date.Now.AddSeconds(30)
While Date.Now < waitUntil
If m_isNavigating = False Then
Exit While
Else
Application.DoEvents()
End If
End While
' See if the navigation timed out.
If m_isNavigating = True Then
m_isNavigating = False
msg = "Timed out while navigating to : " & urlAddress
GSL.FileOperations.logWarning(msg)
Throw New Exception(msg)
End If
' Access the web content.
Log("#Navigation Results..." & vbCrLf &
"Document Title|" & m_browser.CoreWebView2.DocumentTitle & vbCrLf & "" )
' Execute a script to get the webpage contents.
Me.ExecuteScriptAsync("document.documentElement.outerHTML;")
waitUntil = Date.Now.AddSeconds(15)
While m_isScriptProcessing = True And Date.Now < waitUntil
Application.DoEvents()
End While
' See if the script timed out.
If m_isScriptProcessing = True Then
m_isScriptProcessing = False
msg = "Timed out while executing script to extract webpage contents."
GSL.FileOperations.logWarning(msg)
Throw New Exception(msg)
End If
' Return results.
Return m_message
End Function
' Class constructor.
Public Sub New()
' instantiate the browser object.
m_browser = New WebView2
With m_browser
.ZoomFactor = 0.5
End With
' Initialize the browser.
AddHandler m_browser.CoreWebView2Ready, AddressOf EventCoreWebView2Ready
InitializeBrowserAsync()
End Sub
' Private class methods.
' This method initializes the object and only needs to be called once per object instantiation.
Private Async Sub InitializeBrowserAsync()
Await m_browser.EnsureCoreWebView2Async()
End Sub
' This method checks/resolves issues with a URL address, as needed.
Private Function FixURLAddress(urlAddress As String) As String
' Ensure input is valid.
If String.IsNullOrWhiteSpace(urlAddress) = True Then
Return HTTPS & "www.Google.com/"
Else
urlAddress = urlAddress.Trim
End If
' Check for an incomplete URL address.
Dim checkURLAddress As String = urlAddress.ToLower
If checkURLAddress.StartsWith(HTTP) = False AndAlso checkURLAddress.StartsWith(HTTPS) = False Then
urlAddress = HTTPS & urlAddress
End If
' Place a trailing "/", if needed.
If urlAddress.EndsWith("/") = False Then
urlAddress &= "/"
End If
' Return results.
Return urlAddress
End Function
' This method is used to log messages while debug mode is enabled.
Private Sub Log(msg As String)
' Ensure input is valid.
If String.IsNullOrWhiteSpace(msg) = True Or m_isDebugModeEnabled = False Then
Exit Sub
End If
' Reformat the message, as needed.
Dim fields() As String = Nothing
Dim lines() As String = Split(msg, vbCrLf)
msg = Nothing
For Each line As String In lines
' Get the next line to process.
If String.IsNullOrWhiteSpace(line) = True Then
Continue For
ElseIf msg IsNot Nothing Then
msg &= vbCrLf
End If
' See if this is a new header.
If line.StartsWith("#") = True Then
msg &= "==============================" & vbCrLf & line.Replace("#", Nothing)
Continue For
End If
' See if this line contains text to be formatted.
If line.IndexOf("|") > 0 Then
If line.EndsWith("|") = True Then line &= " "
fields = Split(line, "|")
msg &= GSL.FormattingOperations.JustifyText(fields(0), 25, GSL.TextAlignment.Left) & " : " & fields(1)
Continue For
End If
' When we get to here, this is text that is simply appended as is.
msg &= line
Next line
' Log the message.
GSL.FileOperations.logMessage(msg)
End Sub
' This method executes a script and cleans up the results.
Private Async Sub ExecuteScriptAsync(scriptName As String)
' Initialize variables.
m_isScriptProcessing = True
m_message = Nothing
' Execute the script and wait for the results.
Dim msg As String = Await m_browser.ExecuteScriptAsync(scriptName)
If String.IsNullOrWhiteSpace(msg) = True Then
Exit Sub
End If
' Cleanup the results.
' Note the line below was causing an exception, so I had to bail on using JSON for now.
' Ideally I would like to use JSON and somehow get an HTMLDocument object.
' Dim o As Object = System.Text.Json.JsonSerializer.Deserialize(msg, msg.GetType)
msg = System.Text.RegularExpressions.Regex.Unescape(msg)
msg = msg.Remove(0, 1)
msg = msg.Remove(msg.Length - 1, 1)
' Make the results available to the object.
m_isScriptProcessing = False
m_message = msg
End Sub
' These are event callback functions.
Private Sub EventCoreWebView2Ready(sender As Object, e As EventArgs)
' The browser core has been initialized.
m_isInitialized = True
Log("#Browser object initialization completed.")
' Some of these listeners can't be added until the core browser objects have been instantiated.
AddHandler m_browser.NavigationStarting, AddressOf EventNavigationStarting
AddHandler m_browser.NavigationCompleted, AddressOf EventNavigationCompleted
AddHandler m_browser.ContentLoading, AddressOf EventContentLoading
AddHandler m_browser.SourceChanged, AddressOf EventSourceChanged
AddHandler m_browser.WebMessageReceived, AddressOf EventWebMessageReceived
AddHandler m_browser.CoreWebView2.FrameNavigationStarting, AddressOf EventFrameNavigationStarting
AddHandler m_browser.CoreWebView2.FrameNavigationCompleted, AddressOf EventFrameNavigationCompleted
AddHandler m_browser.CoreWebView2.ProcessFailed, AddressOf EventProcessFailed
End Sub
Private Sub EventNavigationStarting(sender As Object, e As CoreWebView2NavigationStartingEventArgs)
m_isNavigating = True
Log("#Navigation starting..." & vbCrLf &
"is Redirected|" & e.IsRedirected & vbCrLf &
"Is User Initiated|" & e.IsUserInitiated & vbCrLf &
"Navigation ID|" & e.NavigationId & vbCrLf &
"Cancel Request Flag|" & e.Cancel & vbCrLf &
"Request Headers|" & vbCrLf &
" From|" & e.RequestHeaders.From & vbCrLf &
" Host|" & e.RequestHeaders.Host & vbCrLf &
"URL Address|" & e.Uri & vbCrLf &
""
)
End Sub
Private Sub EventNavigationCompleted(sender As Object, e As CoreWebView2NavigationCompletedEventArgs)
m_isNavigating = False
Log("#Navigation Completed..." & vbCrLf &
"URL Address|" & m_browser.Source.AbsoluteUri & vbCrLf &
"Is Success|" & e.IsSuccess & vbCrLf &
"Navigation ID|" & e.NavigationId & vbCrLf &
"Error Status|" & e.WebErrorStatus.ToString & vbCrLf &
""
)
End Sub
Private Sub EventContentLoading(sender As Object, e As CoreWebView2ContentLoadingEventArgs)
Log("#Content Loading..." & vbCrLf &
"Is Error Page|" & e.IsErrorPage & vbCrLf &
"Navigation ID|" & e.NavigationId & vbCrLf &
""
)
End Sub
Private Sub EventSourceChanged(sender As Object, e As CoreWebView2SourceChangedEventArgs)
Log("#Source Changed" & vbCrLf &
"Is New Document|" & e.IsNewDocument & vbCrLf &
""
)
End Sub
Private Sub EventWebMessageReceived(sender As Object, e As CoreWebView2WebMessageReceivedEventArgs)
Log("#Message Recieved" & vbCrLf &
"Error Status|" &
"JASON Message|" & e.WebMessageAsJson & vbCrLf &
""
)
Try
Log("Try Message|" & e.TryGetWebMessageAsString)
Catch ex As Exception
End Try
End Sub
Private Sub EventFrameNavigationStarting(sender As Object, e As CoreWebView2NavigationStartingEventArgs)
Log("#Frame Navigation Starting...")
End Sub
Private Sub EventFrameNavigationCompleted(sender As Object, e As CoreWebView2NavigationCompletedEventArgs)
Log("#Frame Navigation Completed")
End Sub
Private Sub EventProcessFailed(sender As Object, e As CoreWebView2ProcessFailedEventArgs)
Log("#Process Failed" & vbCrLf &
"Failure Type|" & e.ProcessFailedKind.ToString & vbCrLf &
""
)
End Sub
End Class
@GoncerAl I do appreciate your pain, but there is no direct way to access the DOM from .Net.
You will have to learn Javascript (not that hard, as all high level languages are convergent).
Exact examples will be hard because results are not objects, only JSON.
For your code, can I suggest not using DoEvents
while waiting, check your processor usage during a DoEvents
to see why.
Here's a generic example to access Javascript, and wait for the result, that I use in this control.
Dim Text = Execute("document.getElementById('myId').innerText")
Function Execute(Script As String) As String
Return Wait(WebView2.ExecuteScriptAsync(Script))
End Function
Function Wait(Task As Task(Of String)) As String
Wait = ""
Task.ContinueWith(
Sub()
If Task.IsFaulted Then
Wait = Task.Exception.Message
Else
Wait = Task.Result
End If
Frame.Continue = False
End Sub)
'Timeout if takes too long
Timer.Enabled = True
Frame.Continue = True
Dispatcher.PushFrame(Frame)
Timer.Enabled = False
End Function
@GoncerAl If you use the control, I have created, it will give you what you need. It inherits from WebView2 so can be used directly in it's place. email me: ukandrewc@gmail.com if you need help getting started with it.
Oops, no DOM access anymore? (Last time I used this was with the IE-based control. I only come back to this when there's a proper HTML engine behind this, what WebView2 seems to be.) I need to disable JavaScript on the content for security reasons. How can I edit and manipulate the HTML content then? Is this impossible? If I wanted to use JavaScript to manipulate the HTML on the page, I'd be using Electron or another framework that lets me write my application in JavaScript altogether.
No DOM - really??? :sob: :sob: :sob: The Webbrowser is outdated and I ended up here hoping to replace it with WebView2. But it turns out that the migration process is just a PAIN, especially if you don't know the javascript: ((((
There is an experimental one for .Net here: https://github.com/ukandrewc/Webview2.Winforms.DOM
This experimental DOM allows that: https://github.com/ukandrewc/Webview2.Winforms.DOM There's no need for
GetElementFromPoint
because in the DOMClickEvent,Document.ActiveElement
will give you any editable element.
DOMClickEvent? How can I not find it? Thank you
There is an experimental one for .Net here: https://github.com/ukandrewc/Webview2.Winforms.DOM
Might be late but how can I use this?
I don't think a ton of JavaScript knowledge is necessary. Break it down to the very simplest things you need JS to do (e.g. get an element, write to an element).
I would probably just write a few small JavaScript wrapper functions that allow for reading and writing from the page and then I would leverage other libraries like HtmlAgilityPack if I wanted to use C# instead of JavaScript inspecting the DOM. As purely thrown together example here I load in a copy of jQuery slim, use it to select an element by ID and then return an HtmlNode
(I probably didn't even need jQuery other than you can use it to select in different ways behond getElementById). With a few more lines a call could be made into a snippit of jQuery to update the DOM in the browser.
Again, I understand this is limited in the sense that, it's a snapshot in time but I'm mainly sharing as a way to show different ways you can interact with the page.
/// <summary>
/// Gets an HTML element by ID.
/// </summary>
/// <param name="id"></param>
public async Task<HtmlNode> GetElementById(string id)
{
// Load in jQuery slim/minified
string jQuery = await System.IO.File.ReadAllTextAsync(@"T:\JavaScript\jquery-3.5.1.slim.min.js");
await webView.CoreWebView2.ExecuteScriptAsync(jQuery);
// Get the element via jQuery.
string html = await webView.CoreWebView2.ExecuteScriptAsync($"$('#{id}').html()");
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
return htmlDoc.DocumentNode;
}
Using this control, you get direct .Net access to the DOM: https://github.com/ukandrewc/Webview2.Winforms.DOM
If you have any questions about using the control, please post in issues for the control, Rather than here which is for pure WebView2
Ugh. Ok... Rant ON
Reading the DOM is no more or less secure than JS injection, but it IS vastly easier to do and for many people the entire goal is just to READ the content, not programmatically change it.
Sorry for sounding pissy, but I've been trying literally for weeks to find a sane solution to what should be a simple problem that WebBrowser actually could do except that Microsoft permanently glued it to IE11 and so can't run a lot of (wait for it) JavaScript routines that aren't properly formed and then they abandoned it. If they just took that control and updated it to use the WebView 2 engine - problem solved (And on that - really - we have to install a freaking second browser engine to make this work? They couldn't have made this use the Edge browser's engine to handle rendering? I mean crappy IE could do it...).
It's cool that someone found a hacky way around this and it kinda works (I mean - come ON - injecting JS and then using Win32's messaging system to get the results back and you seriously brought up cross-platform compatibility?) but this is kinda sad, even by open source standards - and this isn't even open source! (If it were, I'd go fix this.)
There. Rant OFF.
Feel much better.
Now, pardon me while I go bang my head against CEFSharp for a while. It's a cannon to swat a fly but at least it's massively feature complete.
@TheWerewolf Hope you don't mind, but couldn't resist ;-)
One day we'll look back and consider pre webView2 as the good old days. While they were actually worse, nostalgia will make them seem better.
It is possible to create a DOM wrapper that gives you good access to the DOM. I'd offer you mine, but it's Winforms and VB.Net, so you'd hate it ;-)
@TheWerewolf
Just wanted to give a thumbs up for clearing cookies which is a part of the original question.
So, I came here finding out that I can't fully convert my WebBrowser control codes to WebView2 because of the ExecuteScript JS DOM implementation. Sucks though but I hope they implement the easy-DOM way.
Sorry if this has been discussed before, i cannot find the subject anywhere.
webbrowser.document object what is the equivalent in Webview2 ? Can we access the DOM like we did with webbrowser control ?
How can all of the cookies and user data be cleared when the winform program is run ? All of the cookies and data are still in the browser from prior sessions ?
with Webbrowser control we used wininet method
public static bool SupressCookiePersist() { // 3 = INTERNET_SUPPRESS_COOKIE_PERSIST // 81 = INTERNET_OPTION_SUPPRESS_BEHAVIOR return SetOption(81, 3); }
This does not work with Webview2, is there any way to get a fresh browser session when the program is executed ????
Any help would be appreciated.
AB#28556146