Skip to content

Commit

Permalink
more progress
Browse files Browse the repository at this point in the history
  • Loading branch information
kblok committed Nov 14, 2023
1 parent f296e40 commit c5587b4
Show file tree
Hide file tree
Showing 10 changed files with 559 additions and 494 deletions.
799 changes: 421 additions & 378 deletions lib/PuppeteerSharp/ElementHandle.cs

Large diffs are not rendered by default.

30 changes: 2 additions & 28 deletions lib/PuppeteerSharp/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,34 +99,8 @@ await InstallGlobalBindingAsync(new Binding(

internal IJSHandle CreateJSHandle(RemoteObject remoteObject)
=> remoteObject.Subtype == RemoteObjectSubtype.Node && Frame != null
? new ElementHandle(this, Client, remoteObject, Frame, ((Frame)Frame).FrameManager.Page, ((Frame)Frame).FrameManager)
: new JSHandle(this, Client, remoteObject);

internal async Task<IElementHandle> AdoptElementHandleAsync(IElementHandle elementHandle)
{
if (elementHandle.ExecutionContext == this)
{
throw new PuppeteerException("Cannot adopt handle that already belongs to this execution context");
}

if (World == null)
{
throw new PuppeteerException("Cannot adopt handle without DOMWorld");
}

var nodeInfo = await Client.SendAsync<DomDescribeNodeResponse>("DOM.describeNode", new DomDescribeNodeRequest
{
ObjectId = elementHandle.RemoteObject.ObjectId,
}).ConfigureAwait(false);

var obj = await Client.SendAsync<DomResolveNodeResponse>("DOM.resolveNode", new DomResolveNodeRequest
{
BackendNodeId = nodeInfo.Node.BackendNodeId,
ExecutionContextId = ContextId,
}).ConfigureAwait(false);

return CreateJSHandle(obj.Object) as ElementHandle;
}
? new ElementHandle(World, remoteObject)
: new JSHandle(World, remoteObject);

private static string GetExceptionMessage(EvaluateExceptionResponseDetails exceptionDetails)
{
Expand Down
6 changes: 0 additions & 6 deletions lib/PuppeteerSharp/IJSHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ public interface IJSHandle : IAsyncDisposable
/// <value><c>true</c> if disposed; otherwise, <c>false</c>.</value>
bool Disposed { get; }

/// <summary>
/// Gets the execution context.
/// </summary>
/// <value>The execution context.</value>
IExecutionContext ExecutionContext { get; }

/// <summary>
/// Gets the remote object.
/// </summary>
Expand Down
5 changes: 2 additions & 3 deletions lib/PuppeteerSharp/IsolatedWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ internal override async Task<IElementHandle> AdoptBackendNodeAsync(object backen

internal override async Task<IJSHandle> TransferHandleAsync(IJSHandle handle)
{
var context = await GetExecutionContextAsync().ConfigureAwait(false);
if (handle.ExecutionContext == context)
if ((handle as JSHandle).Realm == this)
{
return handle;
}
Expand All @@ -136,7 +135,7 @@ internal override async Task<IJSHandle> AdoptHandleAsync(IJSHandle handle)
{
var executionContext = await GetExecutionContextAsync().ConfigureAwait(false);

if (executionContext == handle.ExecutionContext)
if ((handle as JSHandle).Realm == this)
{
return handle;
}
Expand Down
173 changes: 116 additions & 57 deletions lib/PuppeteerSharp/JSHandle.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
Expand All @@ -14,20 +16,13 @@ namespace PuppeteerSharp
[JsonConverter(typeof(JSHandleMethodConverter))]
public class JSHandle : IJSHandle
{
internal JSHandle(ExecutionContext context, CDPSession client, RemoteObject remoteObject)
internal JSHandle(IsolatedWorld world, RemoteObject remoteObject)
{
ExecutionContext = context;
Client = client;
Logger = Client.Connection.LoggerFactory.CreateLogger(GetType());
Realm = world;
RemoteObject = remoteObject;
}

/// <inheritdoc cref="ExecutionContext"/>
public ExecutionContext ExecutionContext { get; }

/// <inheritdoc/>
IExecutionContext IJSHandle.ExecutionContext => ExecutionContext;

/// <inheritdoc/>
public bool Disposed { get; private set; }

Expand All @@ -38,68 +33,83 @@ internal JSHandle(ExecutionContext context, CDPSession client, RemoteObject remo

internal Func<Task> DisposeAction { get; set; }

internal IsolatedWorld Realm { get; }

internal Frame Frame => Realm.Environment as Frame;

internal string Id => RemoteObject.ObjectId;

/// <summary>
/// Logger.
/// </summary>
protected ILogger Logger { get; }

/// <inheritdoc/>
public async Task<IJSHandle> GetPropertyAsync(string propertyName)
{
var objectHandle = await EvaluateFunctionHandleAsync(
@"(object, propertyName) => {
const result = { __proto__: null};
result[propertyName] = object[propertyName];
return result;
}",
propertyName).ConfigureAwait(false);
var properties = await objectHandle.GetPropertiesAsync().ConfigureAwait(false);
properties.TryGetValue(propertyName, out var result);
await objectHandle.DisposeAsync().ConfigureAwait(false);
return result;
}

/// <inheritdoc/>
public async Task<Dictionary<string, IJSHandle>> GetPropertiesAsync()
{
var response = await Client.SendAsync<RuntimeGetPropertiesResponse>("Runtime.getProperties", new RuntimeGetPropertiesRequest
public Task<IJSHandle> GetPropertyAsync(string propertyName)
=> BindIsolatedHandleAsync(async handle =>
{
ObjectId = RemoteObject.ObjectId,
OwnProperties = true,
}).ConfigureAwait(false);

var result = new Dictionary<string, IJSHandle>();
var objectHandle = await handle.EvaluateFunctionHandleAsync(
@"(object, propertyName) => {
const result = { __proto__: null};
result[propertyName] = object[propertyName];
return result;
}",
propertyName).ConfigureAwait(false);
var properties = await objectHandle.GetPropertiesAsync().ConfigureAwait(false);
properties.TryGetValue(propertyName, out var result);
await objectHandle.DisposeAsync().ConfigureAwait(false);
return result;
});

foreach (var property in response.Result)
/// <inheritdoc/>
public Task<Dictionary<string, IJSHandle>> GetPropertiesAsync()
=> BindIsolatedHandleAsync(async handle =>
{
if (property.Enumerable == null)
var propertyNames = await handle.EvaluateFunctionAsync<string[]>(@"object => {
const enumerableProperties = [];
const descriptors = Object.getOwnPropertyDescriptors(object);
for (const propertyName in descriptors) {
if (descriptors[propertyName]?.enumerable)
{
enumerableProperties.push(propertyName);
}
}
return enumerableProperties;
}").ConfigureAwait(false);

var dic = new Dictionary<string, IJSHandle>();
var results = await Task.WhenAll(propertyNames.Select(key => GetPropertyAsync(key))).ConfigureAwait(false);

foreach (var key in propertyNames)
{
continue;
var handleItem = await GetPropertyAsync(key).ConfigureAwait(false);
if (handleItem is not null)
{
dic.Add(key, handleItem);
}
}

result.Add(property.Name, ExecutionContext.CreateJSHandle(property.Value));
}

return result;
}
return dic;
});

/// <inheritdoc/>
public async Task<object> JsonValueAsync() => await JsonValueAsync<object>().ConfigureAwait(false);

/// <inheritdoc/>
public async Task<T> JsonValueAsync<T>()
{
var objectId = RemoteObject.ObjectId;

if (objectId == null)
public Task<T> JsonValueAsync<T>()
=> BindIsolatedHandleAsync(async handle =>
{
return (T)RemoteObjectHelper.ValueFromRemoteObject<T>(RemoteObject);
}
var objectId = handle.RemoteObject.ObjectId;

var value = await EvaluateFunctionAsync<T>("object => object").ConfigureAwait(false);
if (objectId == null)
{
return (T)RemoteObjectHelper.ValueFromRemoteObject<T>(RemoteObject);
}

return value == null ? throw new PuppeteerException("Could not serialize referenced object") : value;
}
var value = await handle.EvaluateFunctionAsync<T>("object => object").ConfigureAwait(false);

return value == null ? throw new PuppeteerException("Could not serialize referenced object") : value;
});

/// <inheritdoc/>
public async ValueTask DisposeAsync()
Expand Down Expand Up @@ -139,23 +149,72 @@ public Task<IJSHandle> EvaluateFunctionHandleAsync(string pageFunction, params o
{
var list = new List<object>(args);
list.Insert(0, this);
return ExecutionContext.EvaluateFunctionHandleAsync(pageFunction, list.ToArray());
return Realm.EvaluateFunctionHandleAsync(pageFunction, list.ToArray());
}

/// <inheritdoc/>
public Task<JToken> EvaluateFunctionAsync(string script, params object[] args)
=> EvaluateFunctionAsync(script, args, false);

/// <inheritdoc/>
public Task<T> EvaluateFunctionAsync<T>(string script, params object[] args)
{
var list = new List<object>(args);
list.Insert(0, this);
return ExecutionContext.EvaluateFunctionAsync<JToken>(script, list.ToArray());
return Realm.EvaluateFunctionAsync<T>(script, list.ToArray());
}

/// <inheritdoc/>
public Task<T> EvaluateFunctionAsync<T>(string script, params object[] args)
internal async Task<JToken> EvaluateFunctionAsync(string script, object[] args, bool adopt)
{
var list = new List<object>(args);
list.Insert(0, this);
return ExecutionContext.EvaluateFunctionAsync<T>(script, list.ToArray());
var adoptedThis = await Frame.IsolatedRealm.AdoptHandleAsync(this).ConfigureAwait(false);
list.Insert(0, adoptedThis);
return await Frame.IsolatedRealm.EvaluateFunctionAsync<JToken>(script, list.ToArray()).ConfigureAwait(false);
}

internal async Task<T> BindIsolatedHandleAsync<T>(Func<JSHandle, Task<T>> action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}

if (Realm == Frame.IsolatedRealm)
{
return await action(this).ConfigureAwait(false);
}

var adoptedThis = await Frame.IsolatedRealm.AdoptHandleAsync(this).ConfigureAwait(false) as JSHandle;
var result = await action(adoptedThis).ConfigureAwait(false);

if (result is IJSHandle jsHandleResult)
{
// If the function returns `adoptedThis`, then we return `this` and T is a IJSHandle.
if (jsHandleResult == adoptedThis)
{
return (T)(object)this;
}

return (T)(object)await Realm.TransferHandleAsync(jsHandleResult).ConfigureAwait(false);
}

// If the function returns an array of handlers, transfer them into the current realm.
if (typeof(T).IsArray)
{
var enumerable = result as IEnumerable<IJSHandle>;
return (T)(object)await Task.WhenAll(
enumerable.Select(item => item is IJSHandle ? Realm.TransferHandleAsync(item) : Task.FromResult(item))).ConfigureAwait(false);
}

if (result is IDictionary<string, IJSHandle> dictionaryResult)
{
foreach (var key in dictionaryResult.Keys)
{
dictionaryResult[key] = await Realm.TransferHandleAsync(dictionaryResult[key]).ConfigureAwait(false);
}
}

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace PuppeteerSharp.Messaging
{
internal class DomScrollIntoViewIfNeededRequest
{
public string ObjectId { get; set; }
}
}
2 changes: 1 addition & 1 deletion lib/PuppeteerSharp/PuppeteerHandleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ internal static object FormatArgument(this IJSHandle jSHandle, ExecutionContext
throw new PuppeteerException("JSHandle is disposed!");
}

if (jSHandle.ExecutionContext != context)
if ((jSHandle as JSHandle).Realm != context.World)
{
throw new PuppeteerException("JSHandles can be evaluated only in the context they were created!");
}
Expand Down
7 changes: 3 additions & 4 deletions lib/PuppeteerSharp/QueryHandlers/AriaQueryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ public AriaQueryHandler()

internal override async IAsyncEnumerable<IElementHandle> QueryAllAsync(IElementHandle element, string selector)
{
var context = (ExecutionContext)element.ExecutionContext;
var world = context.World;
var elementHandle = element as ElementHandle;
var ariaSelector = ParseAriaSelector(selector);
var results = await QueryAXTreeAsync(context.Client, element, ariaSelector.Name, ariaSelector.Role).ConfigureAwait(false);
var results = await QueryAXTreeAsync(elementHandle.Realm.Environment.Client, element, ariaSelector.Name, ariaSelector.Role).ConfigureAwait(false);

foreach (var item in results)
{
yield return await world.AdoptBackendNodeAsync(item.BackendDOMNodeId).ConfigureAwait(false);
yield return await elementHandle.Realm.AdoptBackendNodeAsync(item.BackendDOMNodeId).ConfigureAwait(false);
}
}

Expand Down
6 changes: 0 additions & 6 deletions lib/PuppeteerSharp/QueryHandlers/QueryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ internal string QuerySelector

internal virtual async Task<IElementHandle> QueryOneAsync(IElementHandle element, string selector)
{
var world = (element.ExecutionContext as ExecutionContext).World
?? throw new PuppeteerException("Element doesn't have a valid world");

var result = await element.EvaluateFunctionHandleAsync(
QuerySelector,
selector,
Expand Down Expand Up @@ -163,9 +160,6 @@ internal virtual async Task<IElementHandle> WaitForAsync(

internal virtual async IAsyncEnumerable<IElementHandle> QueryAllAsync(IElementHandle element, string selector)
{
var world = (element.ExecutionContext as ExecutionContext).World
?? throw new PuppeteerException("Element doesn't have a valid world");

var handle = await element.EvaluateFunctionHandleAsync(
QuerySelectorAll,
selector,
Expand Down
Loading

0 comments on commit c5587b4

Please sign in to comment.