Skip to content

Commit

Permalink
Refactor tree manager
Browse files Browse the repository at this point in the history
  • Loading branch information
kblok committed Apr 19, 2023
1 parent 9268c36 commit 504310f
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 145 deletions.
2 changes: 1 addition & 1 deletion lib/PuppeteerSharp/ElementHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ public async Task<IFrame> ContentFrameAsync()
ObjectId = RemoteObject.ObjectId,
}).ConfigureAwait(false);

return string.IsNullOrEmpty(nodeInfo.Node.FrameId) ? null : await _frameManager.GetFrameAsync(nodeInfo.Node.FrameId).ConfigureAwait(false);
return string.IsNullOrEmpty(nodeInfo.Node.FrameId) ? null : await _frameManager.FrameTree.GetFrameAsync(nodeInfo.Node.FrameId).ConfigureAwait(false);
}

/// <inheritdoc/>
Expand Down
29 changes: 4 additions & 25 deletions lib/PuppeteerSharp/Frame.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using PuppeteerSharp.Helpers;
Expand All @@ -11,22 +10,20 @@ namespace PuppeteerSharp
/// <inheritdoc/>
public class Frame : IFrame
{
private readonly string _parentId;

internal Frame(FrameManager frameManager, string frameId, string parentFrameId, CDPSession client)
{
FrameManager = frameManager;
Id = frameId;
Client = client;
_parentId = parentFrameId;
ParentId = parentFrameId;

LifecycleEvents = new List<string>();

UpdateClient(client);
}

/// <inheritdoc/>
public List<IFrame> ChildFrames => FrameManager.FrameTree.GetChildFrames(Id);
public IEnumerable<IFrame> ChildFrames => FrameManager.FrameTree.GetChildFrames(Id);

/// <inheritdoc/>
public string Name { get; private set; }
Expand All @@ -46,6 +43,8 @@ internal Frame(FrameManager frameManager, string frameId, string parentFrameId,
/// <inheritdoc/>
public string Id { get; internal set; }

internal string ParentId { get; }

internal FrameManager FrameManager { get; }

internal string LoaderId { get; set; }
Expand Down Expand Up @@ -322,22 +321,6 @@ public Task ClickAsync(string selector, ClickOptions options = null)
public Task TypeAsync(string selector, string text, TypeOptions options = null)
=> PuppeteerWorld.TypeAsync(selector, text, options);

internal void AddChildFrame(Frame frame)
{
lock (_childFrames)
{
_childFrames.Add(frame);
}
}

internal void RemoveChildFrame(Frame frame)
{
lock (_childFrames)
{
_childFrames.Remove(frame);
}
}

internal void OnLoadingStarted() => HasStartedLoading = true;

internal void OnLoadingStopped()
Expand Down Expand Up @@ -371,10 +354,6 @@ internal void Detach()
Detached = true;
MainWorld.Detach();
PuppeteerWorld.Detach();
if (ParentFrame != null)
{
((Frame)ParentFrame).RemoveChildFrame(this);
}
}

internal void UpdateClient(CDPSession client)
Expand Down
102 changes: 49 additions & 53 deletions lib/PuppeteerSharp/FrameManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,19 @@ internal class FrameManager

private readonly ConcurrentDictionary<string, ExecutionContext> _contextIdToContext;
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, Frame> _frames;
private readonly AsyncDictionaryHelper<string, Frame> _asyncFrames;
private readonly List<string> _isolatedWorlds = new();
private readonly List<string> _frameNavigatedReceived = new();
private readonly TaskQueue _eventsQueue = new();
private bool _ensureNewDocumentNavigation;

internal FrameManager(CDPSession client, Page page, bool ignoreHTTPSErrors, TimeoutSettings timeoutSettings)
{
Client = client;
Page = page;
_frames = new ConcurrentDictionary<string, Frame>();
_contextIdToContext = new ConcurrentDictionary<string, ExecutionContext>();
_logger = Client.Connection.LoggerFactory.CreateLogger<FrameManager>();
NetworkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
TimeoutSettings = timeoutSettings;
_asyncFrames = new AsyncDictionaryHelper<string, Frame>(_frames, "Frame {0} not found");

Client.MessageReceived += Client_MessageReceived;
}
Expand All @@ -60,7 +57,7 @@ internal FrameManager(CDPSession client, Page page, bool ignoreHTTPSErrors, Time

internal TimeoutSettings TimeoutSettings { get; }

public FrameTree FrameTree { get; internal set; }
internal FrameTree FrameTree { get; private set; }

public async Task<IResponse> NavigateFrameAsync(Frame frame, string url, NavigationOptions options)
{
Expand Down Expand Up @@ -131,7 +128,7 @@ internal async Task InitializeAsync(CDPSession client = null)
getFrameTreeTask,
autoAttachTask).ConfigureAwait(false);

await HandleFrameTreeAsync(client, new FrameTree(getFrameTreeTask.Result.FrameTree)).ConfigureAwait(false);
await HandleFrameTreeAsync(client, getFrameTreeTask.Result.FrameTree).ConfigureAwait(false);

await Task.WhenAll(
client.SendAsync("Page.setLifecycleEventsEnabled", new PageSetLifecycleEventsEnabledRequest { Enabled = true }),
Expand Down Expand Up @@ -175,21 +172,16 @@ internal void OnAttachedToTarget(TargetChangedArgs e)
return;
}

_frames.TryGetValue(e.TargetInfo.TargetId, out var frame);
if (frame != null)
{
frame.UpdateClient(e.Target.Session);
}
var frame = GetFrame(e.TargetInfo.TargetId);
frame?.UpdateClient(e.Target.Session);

e.Target.Session.MessageReceived += Client_MessageReceived;
_ = InitializeAsync(e.Target.Session);
}

internal Frame[] GetFrames() => _frames.Values.ToArray();
internal Frame GetFrame(string frameid) => FrameTree.GetById(frameid);

internal Task<Frame> GetFrameAsync(string frameId) => _asyncFrames.GetItemAsync(frameId);

internal Task<Frame> TryGetFrameAsync(string frameId) => _asyncFrames.TryGetItemAsync(frameId);
internal Frame[] GetFrames() => FrameTree.Frames;

private async Task NavigateAsync(CDPSession client, string url, string referrer, string frameId)
{
Expand Down Expand Up @@ -268,15 +260,14 @@ private void Client_MessageReceived(object sender, MessageEventArgs e)

private void OnFrameStartedLoading(BasicFrameResponse e)
{
if (_frames.TryGetValue(e.FrameId, out var frame))
{
frame.OnLoadingStarted();
}
var frame = GetFrame(e.FrameId);
frame?.OnLoadingStarted();
}

private void OnFrameStoppedLoading(BasicFrameResponse e)
{
if (_frames.TryGetValue(e.FrameId, out var frame))
var frame = GetFrame(e.FrameId);
if (frame != null)
{
frame.OnLoadingStopped();
LifecycleEvent?.Invoke(this, new FrameEventArgs(frame));
Expand All @@ -285,7 +276,8 @@ private void OnFrameStoppedLoading(BasicFrameResponse e)

private void OnLifeCycleEvent(LifecycleEventResponse e)
{
if (_frames.TryGetValue(e.FrameId, out var frame))
var frame = GetFrame(e.FrameId);
if (frame != null)
{
frame.OnLifecycleEvent(e.LoaderId, e.Name);
LifecycleEvent?.Invoke(this, new FrameEventArgs(frame));
Expand Down Expand Up @@ -320,7 +312,7 @@ private void OnExecutionContextDestroyed(int contextId, CDPSession session)
private async Task OnExecutionContextCreatedAsync(ContextPayload contextPayload, CDPSession session)
{
var frameId = contextPayload.AuxData?.FrameId;
var frame = !string.IsNullOrEmpty(frameId) ? await GetFrameAsync(frameId).ConfigureAwait(false) : null;
var frame = !string.IsNullOrEmpty(frameId) ? await FrameTree.GetFrameAsync(frameId).ConfigureAwait(false) : null;
IsolatedWorld world = null;

if (frame != null)
Expand Down Expand Up @@ -352,7 +344,8 @@ private async Task OnExecutionContextCreatedAsync(ContextPayload contextPayload,

private void OnFrameDetached(PageFrameDetachedResponse e)
{
if (_frames.TryGetValue(e.FrameId, out var frame))
var frame = GetFrame(e.FrameId);
if (frame != null)
{
if (e.Reason == FrameDetachedReason.Remove)
{
Expand All @@ -367,17 +360,21 @@ private void OnFrameDetached(PageFrameDetachedResponse e)

private async Task OnFrameNavigatedAsync(FramePayload framePayload)
{
// This is in the event handler upstream.
// It's more consistent having this here.
_frameNavigatedReceived.Add(framePayload.Id);

var isMainFrame = string.IsNullOrEmpty(framePayload.ParentId);
var frame = isMainFrame ? MainFrame : await GetFrameAsync(framePayload.Id).ConfigureAwait(false);
var frame = isMainFrame ? MainFrame : await FrameTree.GetFrameAsync(framePayload.Id).ConfigureAwait(false);

Contract.Assert(isMainFrame || frame != null, "We either navigate top level or have old version of the navigated frame");

// Detach all child frames first.
if (frame != null)
{
while (frame.ChildFrames.Count > 0)
while (frame.ChildFrames.Any())
{
RemoveFramesRecursively(frame.ChildFrames[0]);
RemoveFramesRecursively(frame.ChildFrames.First() as Frame);
}
}

Expand All @@ -386,12 +383,7 @@ private async Task OnFrameNavigatedAsync(FramePayload framePayload)
{
if (frame != null)
{
// Update frame id to retain frame identity on cross-process navigation.
if (frame.Id != null)
{
_frames.TryRemove(frame.Id, out _);
}

FrameTree.RemoveFrame(frame);
frame.Id = framePayload.Id;
}
else
Expand All @@ -400,7 +392,6 @@ private async Task OnFrameNavigatedAsync(FramePayload framePayload)
frame = new Frame(this, null, framePayload.Id, Client);
}

_asyncFrames.AddItem(framePayload.Id, frame);
MainFrame = frame;
}

Expand All @@ -412,7 +403,8 @@ private async Task OnFrameNavigatedAsync(FramePayload framePayload)

private void OnFrameNavigatedWithinDocument(NavigatedWithinDocumentResponse e)
{
if (_frames.TryGetValue(e.FrameId, out var frame))
var frame = GetFrame(e.FrameId);
if (frame != null)
{
frame.NavigatedWithinDocument(e.Url);

Expand All @@ -422,15 +414,15 @@ private void OnFrameNavigatedWithinDocument(NavigatedWithinDocumentResponse e)
}
}

private void RemoveFramesRecursively(IFrame frame)
private void RemoveFramesRecursively(Frame frame)
{
while (frame.ChildFrames.Count > 0)
while (frame.ChildFrames.Any())
{
RemoveFramesRecursively(frame.ChildFrames[0]);
RemoveFramesRecursively(frame.ChildFrames.First() as Frame);
}

((Frame)frame).Detach();
_frames.TryRemove(frame.Id, out _);
frame.Detach();
FrameTree.RemoveFrame(frame);
FrameDetached?.Invoke(this, new FrameEventArgs(frame));
}

Expand All @@ -439,37 +431,41 @@ private void OnFrameAttached(CDPSession session, PageFrameAttachedResponse frame

private void OnFrameAttached(CDPSession session, string frameId, string parentFrameId)
{
if (_frames.TryGetValue(frameId, out var existingFrame))
var frame = GetFrame(frameId);
if (frame != null)
{
if (session != null && existingFrame.IsOopFrame)
if (session != null && frame.IsOopFrame)
{
existingFrame.UpdateClient(session);
frame.UpdateClient(session);
}

return;
}

if (!_frames.ContainsKey(frameId) && _frames.ContainsKey(parentFrameId))
{
var parentFrame = _frames[parentFrameId];
var frame = new Frame(this, parentFrame, frameId, session);
_asyncFrames.AddItem(frame.Id, frame);
FrameAttached?.Invoke(this, new FrameEventArgs(frame));
}
frame = new Frame(this, frameId, parentFrameId, session);
FrameTree.AddFrame(frame);
FrameAttached?.Invoke(this, new FrameEventArgs(frame));
}

private async Task HandleFrameTreeAsync(CDPSession session, FrameTree frameTree)
private async Task HandleFrameTreeAsync(CDPSession session, PageGetFrameTree frameTree)
{
if (!string.IsNullOrEmpty(frameTree.Frame.ParentId))
{
OnFrameAttached(session, frameTree.Frame.Id, frameTree.Frame.ParentId);
}

await OnFrameNavigatedAsync(frameTree.Frame).ConfigureAwait(false);
if (!_frameNavigatedReceived.Contains(frameTree.Frame.Id))
{
await OnFrameNavigatedAsync(frameTree.Frame).ConfigureAwait(false);
}
else
{
_frameNavigatedReceived.Remove(frameTree.Frame.Id);
}

if (frameTree.Childs != null)
if (frameTree.ChildFrames != null)
{
foreach (var child in frameTree.Childs)
foreach (var child in frameTree.ChildFrames)
{
await HandleFrameTreeAsync(session, child).ConfigureAwait(false);
}
Expand Down
Loading

0 comments on commit 504310f

Please sign in to comment.