CefSharp.Dom icon indicating copy to clipboard operation
CefSharp.Dom copied to clipboard

ArrayHandle null trying to enumerate <li/> menu items

Open mwpowellhtx opened this issue 1 year ago • 7 comments

To frame the opportunity at hand, I tried the same thing in the Chrome dev tools in browser, and I get the range back that I am expecting.

// AH is null querying for the <li/> from the parent menu.
public async Task<T[]> QuerySelectorAllAsync<T>(string selector)
    where T : Element
{
    var arrayHandle = await EvaluateFunctionHandleInternalAsync<DomHandle>(
        "(element, selector) => element.querySelectorAll(selector)",
        selector).ConfigureAwait(false);

    // AH is null:
    var properties = await arrayHandle.GetArray<T>().ConfigureAwait(false);
    //                     ^^^^^^^^^^^
    await arrayHandle.DisposeAsync().ConfigureAwait(false);

    return properties.ToArray();
}

In my calling code:

// Selector "#menu", does return a valid element construct, AFAIK from debug inspection:
var ulMenu = await context.QuerySelectorAsync<HtmlUnorderedListElement>(S.Menu);

// Selector "li", null ref exception thrown from within, expecting HtmlListItemElement[].
var liCandidates = await ulMenu.QuerySelectorAllAsync<HtmlListItemElement>(S.Li);

// Will turn around and LINQ filter based on some other criteria, but not getting anywhere near that far.
var liServerItems = liCandidates.Where(ListItemWithImage).ToArray();
//                                     ^^^^^^^^^^^^^^^^^

I'm doing that correctly, yes? I want all the <li/> elements child to the ulMenu. Everything else being equal, I promise you it "should be" returning valid elements, from the Chrome console:

document.querySelector("#menu").querySelectorAll("li")
// #menu: <ul class="..." id="menu" />
// li: NodeList(20) [li.mm-active, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li]

There is obviously more within each of these elements, abbreviated for brevity.

mwpowellhtx avatar Jul 08 '24 01:07 mwpowellhtx

Another data point for you, for giggles I tried the following.

var liContext = await context.QuerySelectorAllAsync<HtmlListItemElement>(S.Li);

And it "works". Sort it, but it will not work for us, I think, because these are "all" of the <li/>, not just the ones subordinate to the ulMenu that we want.

Maybe there is a better way to get those children, I do not know.

mwpowellhtx avatar Jul 08 '24 02:07 mwpowellhtx

Note that the <li/> children are not all flat; it would be considered best for a depth first recursive search. I'm assuming that's how the console does it, but I could be mistaken.

mwpowellhtx avatar Jul 08 '24 02:07 mwpowellhtx

After poking around a bit and exploring the query and child paths available, this one will work, and I rather prefer it actually. Seems to be focused on the direct child elements of the specified type, which is perfect, just what we need. Not that the other one ought not to work, in principle, but this one does as well, FWIW.

var ulMenu = await context.QuerySelectorAsync<HtmlUnorderedListElement>(S.Menu);
var liServerItems = await (await ulMenu.GetChildrenAsync<HtmlListItemElement>()).ToArrayAsync();
// ...and on from there, drilled through a couple other use cases, including another nested <li/>, also successfully.

The only thing I dislike about it, the lack of selector comprehension, in the event we need to provide more discernment there, but for what we are doing, I think it will be sufficient.

mwpowellhtx avatar Jul 08 '24 02:07 mwpowellhtx

I'm doing that correctly, yes?

Your code looks fine, likely it's a bug. querySelectorAll returns a NodeList and there's currently no mapping for that yet.

The other QuerySelectorAllAsync methods do something slightly different. Something like the following should work.

var arrayHandle = await Handle.EvaluateFunctionHandleAsync(
    "(element, selector) => element.querySelectorAll(selector)",
    selector).ConfigureAwait(false);

var properties = await arrayHandle.GetPropertiesAsync().ConfigureAwait(false);
await arrayHandle.DisposeAsync().ConfigureAwait(false);

return properties.Select(kvp => kvp.Value.ToDomHandle<T>()).ToArray();

And it "works". Sort it, but it will not work for us, I think, because these are "all" of the <li/>, not just the ones subordinate to the ulMenu that we want.

Maybe there is a better way to get those children, I do not know.

You know querySelectorAll supports css selectors? You should be able to make a single querySelectorAll call

amaitland avatar Jul 12 '24 09:07 amaitland

Awesome; duly noted, for future reference. Thank you... 🍻

mwpowellhtx avatar Jul 12 '24 15:07 mwpowellhtx

We should fix the current implementation.

amaitland avatar Jul 12 '24 19:07 amaitland

Cool. That'd be great, overall in general.

Although, I do have a workaround, which I think is a bit better suited to the target DOM in question.

mwpowellhtx avatar Jul 12 '24 20:07 mwpowellhtx