Skip to content

Commit ff23f14

Browse files
kblokactions-user
andauthored
Split frame class (#2573)
* Split frame class * Docs changes --------- Co-authored-by: GitHub Action <[email protected]>
1 parent c354cd4 commit ff23f14

File tree

8 files changed

+403
-328
lines changed

8 files changed

+403
-328
lines changed

lib/PuppeteerSharp/Cdp/CdpElementHandle.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ namespace PuppeteerSharp.Cdp;
3333
/// <inheritdoc />
3434
public class CdpElementHandle : ElementHandle
3535
{
36+
private readonly CdpFrame _cdpFrame;
37+
3638
internal CdpElementHandle(
3739
IsolatedWorld world,
3840
RemoteObject remoteObject) : base(world, remoteObject)
3941
{
4042
Handle = new CdpJSHandle(world, remoteObject);
4143
Logger = Realm.Environment.Client.Connection.LoggerFactory.CreateLogger(GetType());
44+
_cdpFrame = Realm.Frame as CdpFrame;
4245
}
4346

4447
/// <summary>
@@ -50,11 +53,11 @@ internal CdpElementHandle(
5053
Client.Connection.CustomQuerySelectorRegistry;
5154

5255
/// <inheritdoc/>
53-
protected override Page Page => Frame.FrameManager.Page;
56+
protected override Page Page => _cdpFrame.FrameManager.Page;
5457

5558
private CDPSession Client => Handle.Realm.Environment.Client;
5659

57-
private FrameManager FrameManager => Frame.FrameManager;
60+
private FrameManager FrameManager => _cdpFrame.FrameManager;
5861

5962
/// <inheritdoc/>
6063
public override async Task<IFrame> ContentFrameAsync()

lib/PuppeteerSharp/Cdp/CdpFrame.cs

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
// * MIT License
2+
// *
3+
// * Copyright (c) Darío Kondratiuk
4+
// *
5+
// * Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// * of this software and associated documentation files (the "Software"), to deal
7+
// * in the Software without restriction, including without limitation the rights
8+
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// * copies of the Software, and to permit persons to whom the Software is
10+
// * furnished to do so, subject to the following conditions:
11+
// *
12+
// * The above copyright notice and this permission notice shall be included in all
13+
// * copies or substantial portions of the Software.
14+
// *
15+
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// * SOFTWARE.
22+
23+
using System;
24+
using System.Collections.Generic;
25+
using System.Threading.Tasks;
26+
using Microsoft.Extensions.Logging;
27+
using PuppeteerSharp.Cdp.Messaging;
28+
using PuppeteerSharp.Helpers;
29+
30+
namespace PuppeteerSharp.Cdp;
31+
32+
/// <inheritdoc />
33+
public class CdpFrame : Frame
34+
{
35+
private const string RefererHeaderName = "referer";
36+
37+
internal CdpFrame(FrameManager frameManager, string frameId, string parentFrameId, CDPSession client)
38+
{
39+
FrameManager = frameManager;
40+
Id = frameId;
41+
Client = client;
42+
ParentId = parentFrameId;
43+
44+
UpdateClient(client);
45+
46+
FrameSwappedByActivation += (_, _) =>
47+
{
48+
// Emulate loading process for swapped frames.
49+
OnLoadingStarted();
50+
OnLoadingStopped();
51+
};
52+
53+
Logger = client.Connection.LoggerFactory.CreateLogger<Frame>();
54+
}
55+
56+
/// <inheritdoc />
57+
public override CDPSession Client { get; protected set; }
58+
59+
/// <inheritdoc/>
60+
public override IPage Page => FrameManager.Page;
61+
62+
/// <inheritdoc/>
63+
public override bool IsOopFrame => Client != FrameManager.Client;
64+
65+
/// <inheritdoc/>
66+
public override IReadOnlyCollection<IFrame> ChildFrames => FrameManager.FrameTree.GetChildFrames(Id);
67+
68+
internal FrameManager FrameManager { get; }
69+
70+
internal override Frame ParentFrame => FrameManager.FrameTree.GetParentFrame(Id);
71+
72+
/// <inheritdoc/>
73+
public override async Task<IResponse> GoToAsync(string url, NavigationOptions options)
74+
{
75+
var ensureNewDocumentNavigation = false;
76+
77+
if (options == null)
78+
{
79+
throw new ArgumentNullException(nameof(options));
80+
}
81+
82+
var referrer = string.IsNullOrEmpty(options.Referer)
83+
? FrameManager.NetworkManager.ExtraHTTPHeaders?.GetValueOrDefault(RefererHeaderName)
84+
: options.Referer;
85+
var referrerPolicy = string.IsNullOrEmpty(options.ReferrerPolicy)
86+
? FrameManager.NetworkManager.ExtraHTTPHeaders?.GetValueOrDefault("referer-policy")
87+
: options.ReferrerPolicy;
88+
var timeout = options.Timeout ?? FrameManager.TimeoutSettings.NavigationTimeout;
89+
90+
using var watcher = new LifecycleWatcher(FrameManager.NetworkManager, this, options.WaitUntil, timeout);
91+
try
92+
{
93+
var navigateTask = NavigateAsync();
94+
var task = await Task.WhenAny(
95+
watcher.TerminationTask,
96+
navigateTask).ConfigureAwait(false);
97+
98+
await task.ConfigureAwait(false);
99+
100+
task = await Task.WhenAny(
101+
watcher.TerminationTask,
102+
ensureNewDocumentNavigation ? watcher.NewDocumentNavigationTask : watcher.SameDocumentNavigationTask).ConfigureAwait(false);
103+
104+
await task.ConfigureAwait(false);
105+
}
106+
catch (Exception ex)
107+
{
108+
throw new NavigationException(ex.Message, ex);
109+
}
110+
111+
return watcher.NavigationResponse;
112+
113+
async Task NavigateAsync()
114+
{
115+
var response = await Client.SendAsync<PageNavigateResponse>("Page.navigate", new PageNavigateRequest
116+
{
117+
Url = url,
118+
Referrer = referrer ?? string.Empty,
119+
ReferrerPolicy = referrerPolicy ?? string.Empty,
120+
FrameId = Id,
121+
}).ConfigureAwait(false);
122+
123+
ensureNewDocumentNavigation = !string.IsNullOrEmpty(response.LoaderId);
124+
125+
if (!string.IsNullOrEmpty(response.ErrorText) && response.ErrorText != "net::ERR_HTTP_RESPONSE_CODE_FAILURE")
126+
{
127+
throw new NavigationException(response.ErrorText, url);
128+
}
129+
}
130+
}
131+
132+
/// <inheritdoc/>
133+
public override async Task<IResponse> WaitForNavigationAsync(NavigationOptions options = null)
134+
{
135+
var timeout = options?.Timeout ?? FrameManager.TimeoutSettings.NavigationTimeout;
136+
using var watcher = new LifecycleWatcher(FrameManager.NetworkManager, this, options?.WaitUntil, timeout);
137+
var raceTask = await Task.WhenAny(
138+
watcher.NewDocumentNavigationTask,
139+
watcher.SameDocumentNavigationTask,
140+
watcher.TerminationTask).ConfigureAwait(false);
141+
142+
await raceTask.ConfigureAwait(false);
143+
144+
return watcher.NavigationResponse;
145+
}
146+
147+
/// <inheritdoc/>
148+
public override async Task SetContentAsync(string html, NavigationOptions options = null)
149+
{
150+
var waitUntil = options?.WaitUntil ?? new[] { WaitUntilNavigation.Load };
151+
var timeout = options?.Timeout ?? FrameManager.TimeoutSettings.NavigationTimeout;
152+
153+
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
154+
// lifecycle event. @see https://crrev.com/608658
155+
await IsolatedRealm.EvaluateFunctionAsync(
156+
@"html => {
157+
document.open();
158+
document.write(html);
159+
document.close();
160+
}",
161+
html).ConfigureAwait(false);
162+
163+
using var watcher = new LifecycleWatcher(FrameManager.NetworkManager, this, waitUntil, timeout);
164+
var watcherTask = await Task.WhenAny(
165+
watcher.TerminationTask,
166+
watcher.LifecycleTask).ConfigureAwait(false);
167+
168+
await watcherTask.ConfigureAwait(false);
169+
}
170+
171+
/// <inheritdoc/>
172+
public override async Task<IElementHandle> AddStyleTagAsync(AddTagOptions options)
173+
{
174+
if (options == null)
175+
{
176+
throw new ArgumentNullException(nameof(options));
177+
}
178+
179+
if (string.IsNullOrEmpty(options.Url) && string.IsNullOrEmpty(options.Path) && string.IsNullOrEmpty(options.Content))
180+
{
181+
throw new ArgumentException("Provide options with a `Url`, `Path` or `Content` property");
182+
}
183+
184+
var content = options.Content;
185+
186+
if (!string.IsNullOrEmpty(options.Path))
187+
{
188+
content = await AsyncFileHelper.ReadAllText(options.Path).ConfigureAwait(false);
189+
content += "//# sourceURL=" + options.Path.Replace("\n", string.Empty);
190+
}
191+
192+
var handle = await IsolatedRealm.EvaluateFunctionHandleAsync(
193+
@"async (puppeteerUtil, url, id, type, content) => {
194+
const createDeferredPromise = puppeteerUtil.createDeferredPromise;
195+
const promise = createDeferredPromise();
196+
let element;
197+
if (!url) {
198+
element = document.createElement('style');
199+
element.appendChild(document.createTextNode(content));
200+
} else {
201+
const link = document.createElement('link');
202+
link.rel = 'stylesheet';
203+
link.href = url;
204+
element = link;
205+
}
206+
element.addEventListener(
207+
'load',
208+
() => {
209+
promise.resolve();
210+
},
211+
{once: true}
212+
);
213+
element.addEventListener(
214+
'error',
215+
event => {
216+
promise.reject(
217+
new Error(
218+
event.message ?? 'Could not load style'
219+
)
220+
);
221+
},
222+
{once: true}
223+
);
224+
document.head.appendChild(element);
225+
await promise;
226+
return element;
227+
}",
228+
new LazyArg(async context => await context.GetPuppeteerUtilAsync().ConfigureAwait(false)),
229+
options.Url,
230+
options.Id,
231+
options.Type,
232+
content).ConfigureAwait(false);
233+
234+
return (await MainRealm.TransferHandleAsync(handle).ConfigureAwait(false)) as IElementHandle;
235+
}
236+
237+
/// <inheritdoc/>
238+
public override async Task<IElementHandle> AddScriptTagAsync(AddTagOptions options)
239+
{
240+
if (options == null)
241+
{
242+
throw new ArgumentNullException(nameof(options));
243+
}
244+
245+
if (string.IsNullOrEmpty(options.Url) && string.IsNullOrEmpty(options.Path) && string.IsNullOrEmpty(options.Content))
246+
{
247+
throw new ArgumentException("Provide options with a `Url`, `Path` or `Content` property");
248+
}
249+
250+
var content = options.Content;
251+
252+
if (!string.IsNullOrEmpty(options.Path))
253+
{
254+
content = await AsyncFileHelper.ReadAllText(options.Path).ConfigureAwait(false);
255+
content += "//# sourceURL=" + options.Path.Replace("\n", string.Empty);
256+
}
257+
258+
var handle = await IsolatedRealm.EvaluateFunctionHandleAsync(
259+
@"async (puppeteerUtil, url, id, type, content) => {
260+
const createDeferredPromise = puppeteerUtil.createDeferredPromise;
261+
const promise = createDeferredPromise();
262+
const script = document.createElement('script');
263+
script.type = type;
264+
script.text = content;
265+
if (url) {
266+
script.src = url;
267+
script.addEventListener(
268+
'load',
269+
() => {
270+
return promise.resolve();
271+
},
272+
{once: true}
273+
);
274+
script.addEventListener(
275+
'error',
276+
event => {
277+
promise.reject(
278+
new Error(event.message ?? 'Could not load script')
279+
);
280+
},
281+
{once: true}
282+
);
283+
} else {
284+
promise.resolve();
285+
}
286+
if (id) {
287+
script.id = id;
288+
}
289+
document.head.appendChild(script);
290+
await promise;
291+
return script;
292+
}",
293+
new LazyArg(async context => await context.GetPuppeteerUtilAsync().ConfigureAwait(false)),
294+
options.Url,
295+
options.Id,
296+
options.Type,
297+
content).ConfigureAwait(false);
298+
299+
return (await MainRealm.TransferHandleAsync(handle).ConfigureAwait(false)) as IElementHandle;
300+
}
301+
302+
internal void UpdateClient(CDPSession client, bool keepWorlds = false)
303+
{
304+
Client = client;
305+
306+
if (!keepWorlds)
307+
{
308+
MainWorld?.ClearContext();
309+
PuppeteerWorld?.ClearContext();
310+
311+
MainRealm = new IsolatedWorld(
312+
this,
313+
null,
314+
FrameManager.TimeoutSettings,
315+
true);
316+
317+
IsolatedRealm = new IsolatedWorld(
318+
this,
319+
null,
320+
FrameManager.TimeoutSettings,
321+
false);
322+
}
323+
else
324+
{
325+
MainWorld.FrameUpdated();
326+
PuppeteerWorld.FrameUpdated();
327+
}
328+
}
329+
330+
/// <inheritdoc />
331+
protected internal override DeviceRequestPromptManager GetDeviceRequestPromptManager()
332+
{
333+
if (IsOopFrame)
334+
{
335+
return FrameManager.GetDeviceRequestPromptManager(Client);
336+
}
337+
338+
if (ParentFrame == null)
339+
{
340+
throw new PuppeteerException("Unable to find parent frame");
341+
}
342+
343+
return ParentFrame.GetDeviceRequestPromptManager();
344+
}
345+
}

0 commit comments

Comments
 (0)