cefsharpchromium-embeddeddom-manipulation

Does CefSharp implemented DOM manipulation (late 2020)?


Just got started with CefSharp, and I searched for hours about how to manipulate the DOM. All the information I was able to find is kind of outdated, and stating that the only way to do DOM Manipulation is through JavaScript injection (with methods like ExecuteJavaScriptAsync and EvaluateScriptAsync).

The most recent info is more than 2 year old: Any reason to prefer CefSharp over CefGlue (or vice-versa)?
Another one from official github page (even older): https://github.com/cefsharp/CefSharp/issues/1587

But looking at the CEF source code, we can see that there is implemented some perfectly suitable methods to do exactly that, like:

virtual CefRefPtr<CefDOMNode> GetElementById(const CefString& id)

(source code url: https://bitbucket.org/chromiumembedded/cef/src/master/include/cef_dom.h?at=master&fileviewer=file-view-default)

So, to sum up: Since the CEF implemented methods to execute DOM manipulation, and CefSharp community seems to be very active and pushing frequent updates, and all information I could find on StackOverflow and Google are (kind of) outdated, does anyone knows if CefSharp currently (year 2020, version 85.3.130) already implemented any kind of DOM manipulation besides using JavaScript?


Solution

  • does anyone knows if CefSharp currently (year 2020, version 85.3.130) already implemented any kind of DOM manipulation besides using JavaScript?

    As of 2022 there is an alternative to using JavaScript to access the DOM when using CefSharp. You can now use CefSharp.Dom which is an asynchronous library for accessing the DOM. The package requires CefSharp 104.4.180 or greater.

    It uses the Chromium DevTools Protocol and originally started out as a port of Puppeteer Sharp.

    It's freely available on Nuget.

    It also supports keyboard, mouse and touch emulation for automating websites.

    The following examples are an excerpt from the readme.

    // Add using CefSharp.Dom to access CreateDevToolsContextAsync and related extension methods.
    await using var devToolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync();
    
    // Get element by Id
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
    var element = await devToolsContext.QuerySelectorAsync<HtmlElement>("#myElementId");
    
    //Strongly typed element types (this is only a subset of the types mapped)
    var htmlDivElement = await devToolsContext.QuerySelectorAsync<HtmlDivElement>("#myDivElementId");
    var htmlSpanElement = await devToolsContext.QuerySelectorAsync<HtmlSpanElement>("#mySpanElementId");
    var htmlSelectElement = await devToolsContext.QuerySelectorAsync<HtmlSelectElement>("#mySelectElementId");
    var htmlInputElement = await devToolsContext.QuerySelectorAsync<HtmlInputElement>("#myInputElementId");
    var htmlFormElement = await devToolsContext.QuerySelectorAsync<HtmlFormElement>("#myFormElementId");
    var htmlAnchorElement = await devToolsContext.QuerySelectorAsync<HtmlAnchorElement>("#myAnchorElementId");
    var htmlImageElement = await devToolsContext.QuerySelectorAsync<HtmlImageElement>("#myImageElementId");
    var htmlTextAreaElement = await devToolsContext.QuerySelectorAsync<HtmlImageElement>("#myTextAreaElementId");
    var htmlButtonElement = await devToolsContext.QuerySelectorAsync<HtmlButtonElement>("#myButtonElementId");
    var htmlParagraphElement = await devToolsContext.QuerySelectorAsync<HtmlParagraphElement>("#myParagraphElementId");
    var htmlTableElement = await devToolsContext.QuerySelectorAsync<HtmlTableElement>("#myTableElementId");
    
    // Get a custom attribute value
    var customAttribute = await element.GetAttributeAsync<string>("data-customAttribute");
    
    //Set innerText property for the element
    await element.SetInnerTextAsync("Welcome!");
    
    //Get innerText property for the element
    var innerText = await element.GetInnerTextAsync();
    
    //Get all child elements
    var childElements = await element.QuerySelectorAllAsync("div");
    
    //Change CSS style background colour
    await element.EvaluateFunctionAsync("e => e.style.backgroundColor = 'yellow'");
    
    //Type text in an input field
    await element.TypeAsync("Welcome to my Website!");
    
    //Click The element
    await element.ClickAsync();
    
    // Simple way of chaining method calls together when you don't need a handle to the HtmlElement
    var htmlButtonElementInnerText = await devToolsContext.QuerySelectorAsync<HtmlButtonElement>("#myButtonElementId")
        .AndThen(x => x.GetInnerTextAsync());
    
    //Event Handler
    //Expose a function to javascript, functions persist across navigations
    //So only need to do this once
    await devToolsContext.ExposeFunctionAsync("jsAlertButtonClick", () =>
    {
        _ = devToolsContext.EvaluateExpressionAsync("window.alert('Hello! You invoked window.alert()');");
    });
    
    var jsAlertButton = await devToolsContext.QuerySelectorAsync<HtmlButtonElement>("#jsAlertButton");
    
    //Write up the click event listner to call our exposed function
    _ = jsAlertButton.AddEventListenerAsync("click", "jsAlertButtonClick");
    
    //Get a collection of HtmlElements
    var divElements = await devToolsContext.QuerySelectorAllAsync<HtmlDivElement>("div");
    
    foreach (var div in divElements)
    {
        // Get a reference to the CSSStyleDeclaration
        var style = await div.GetStyleAsync();
    
        //Set the border to 1px solid red
        await style.SetPropertyAsync("border", "1px solid red", important: true);
    
        await div.SetAttributeAsync("data-customAttribute", "123");
        await div.SetInnerTextAsync("Updated Div innerText");
    }
    
    //Using standard array
    var tableRows = await htmlTableElement.GetRowsAsync().ToArrayAsync();
    
    foreach (var row in tableRows)
    {
        var cells = await row.GetCellsAsync().ToArrayAsync();
        foreach (var cell in cells)
        {
            var newDiv = await devToolsContext.CreateHtmlElementAsync<HtmlDivElement>("div");
            await newDiv.SetInnerTextAsync("New Div Added!");
            await cell.AppendChildAsync(newDiv);
        }
    }
    
    //Get a reference to the HtmlCollection and use async enumerable
    //Requires Net Core 3.1 or higher
    var tableRowsHtmlCollection = await htmlTableElement.GetRowsAsync();
    
    await foreach (var row in tableRowsHtmlCollection)
    {
        var cells = await row.GetCellsAsync();
        await foreach (var cell in cells)
        {
            var newDiv = await devToolsContext.CreateHtmlElementAsync<HtmlDivElement>("div");
            await newDiv.SetInnerTextAsync("New Div Added!");
            await cell.AppendChildAsync(newDiv);
        }
    }