Skip to content

Commit e9815cd

Browse files
authored
Bidi workers (#3011)
* Bidi workers * fix * fix
1 parent a284969 commit e9815cd

File tree

8 files changed

+227
-25
lines changed

8 files changed

+227
-25
lines changed

lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.local.json

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,21 +1312,6 @@
13121312
"FAIL"
13131313
]
13141314
},
1315-
{
1316-
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
1317-
"testIdPattern": "[worker.spec] *",
1318-
"platforms": [
1319-
"darwin",
1320-
"linux",
1321-
"win32"
1322-
],
1323-
"parameters": [
1324-
"webDriverBiDi"
1325-
],
1326-
"expectations": [
1327-
"FAIL"
1328-
]
1329-
},
13301315
{
13311316
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
13321317
"testIdPattern": "[ChromeLauncher.test.ts] *",

lib/PuppeteerSharp/Bidi/BidiFrame.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class BidiFrame : Frame
3838
private readonly ConcurrentDictionary<BrowsingContext, BidiFrame> _frames = new();
3939
private readonly Realms _realms;
4040

41-
internal BidiFrame(BidiPage parentPage, BidiFrame parentFrame, BrowsingContext browsingContext)
41+
private BidiFrame(BidiPage parentPage, BidiFrame parentFrame, BrowsingContext browsingContext)
4242
{
4343
Client = new BidiCdpSession(this, parentPage?.BidiBrowser?.LoggerFactory ?? parentFrame?.BidiPage?.BidiBrowser?.LoggerFactory);
4444
ParentPage = parentPage;
@@ -604,6 +604,17 @@ private void Initialize()
604604
BidiPage.OnPageError(new PageErrorEventArgs(fullStack));
605605
}
606606
};
607+
608+
// Wire up worker events
609+
BrowsingContext.Worker += (_, args) =>
610+
{
611+
var worker = BidiWebWorker.From(this, args.Realm);
612+
args.Realm.Destroyed += (_, _) =>
613+
{
614+
BidiPage.OnWorkerDestroyed(worker);
615+
};
616+
BidiPage.OnWorkerCreated(worker);
617+
};
607618
}
608619

609620
private void CreateFrameTarget(BrowsingContext browsingContext)
@@ -612,7 +623,7 @@ private void CreateFrameTarget(BrowsingContext browsingContext)
612623
_frames.TryAdd(browsingContext, frame);
613624
((BidiPage)Page).OnFrameAttached(new FrameEventArgs(frame));
614625

615-
browsingContext.Closed += (sender, args) =>
626+
browsingContext.Closed += (_, _) =>
616627
{
617628
_frames.TryRemove(browsingContext, out var _);
618629
};

lib/PuppeteerSharp/Bidi/BidiFrameRealm.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ internal class BidiFrameRealm(WindowRealm realm, BidiFrame frame) : BidiRealm(re
3535

3636
internal BidiFrame Frame => frame;
3737

38+
internal WindowRealm WindowRealm => _realm;
39+
3840
public static BidiFrameRealm From(WindowRealm realm, BidiFrame frame)
3941
{
4042
var frameRealm = new BidiFrameRealm(realm, frame);

lib/PuppeteerSharp/Bidi/BidiPage.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace PuppeteerSharp.Bidi;
3636
/// <inheritdoc />
3737
public class BidiPage : Page
3838
{
39+
private readonly ConcurrentDictionary<string, BidiWebWorker> _workers = new();
3940
private readonly CdpEmulationManager _cdpEmulationManager;
4041
private InternalNetworkConditions _emulatedNetworkConditions;
4142
private TaskCompletionSource<bool> _closedTcs;
@@ -74,7 +75,7 @@ public override IFrame[] Frames
7475
}
7576

7677
/// <inheritdoc />
77-
public override WebWorker[] Workers { get; }
78+
public override WebWorker[] Workers => _workers.Values.ToArray();
7879

7980
/// <inheritdoc />
8081
public override bool IsJavaScriptEnabled { get; }
@@ -518,6 +519,18 @@ internal static BidiPage From(BidiBrowserContext browserContext, BrowsingContext
518519

519520
internal new void OnPageError(PageErrorEventArgs e) => base.OnPageError(e);
520521

522+
internal void OnWorkerCreated(BidiWebWorker worker)
523+
{
524+
_workers[worker.RealmId] = worker;
525+
base.OnWorkerCreated(worker);
526+
}
527+
528+
internal void OnWorkerDestroyed(BidiWebWorker worker)
529+
{
530+
_workers.TryRemove(worker.RealmId, out var _);
531+
base.OnWorkerDestroyed(worker);
532+
}
533+
521534
/// <inheritdoc />
522535
protected override Task<byte[]> PdfInternalAsync(string file, PdfOptions options) => throw new NotImplementedException();
523536

@@ -669,5 +682,24 @@ private void Initialize()
669682
OnClose();
670683
IsClosed = true;
671684
};
685+
686+
// Track workers
687+
WorkerCreated += (_, e) =>
688+
{
689+
var worker = e.Worker as BidiWebWorker;
690+
if (worker != null)
691+
{
692+
_workers[worker.RealmId] = worker;
693+
}
694+
};
695+
696+
WorkerDestroyed += (_, e) =>
697+
{
698+
var worker = e.Worker as BidiWebWorker;
699+
if (worker != null)
700+
{
701+
_workers.TryRemove(worker.RealmId, out var _);
702+
}
703+
};
672704
}
673705
}

lib/PuppeteerSharp/Bidi/BidiWebWorker.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,46 @@
2020
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
// * SOFTWARE.
2222

23+
using System;
2324
using System.Threading.Tasks;
25+
using PuppeteerSharp.Bidi.Core;
2426

2527
namespace PuppeteerSharp.Bidi;
2628

2729
internal class BidiWebWorker : WebWorker
2830
{
29-
public BidiWebWorker(string url) : base(url)
31+
private readonly BidiFrame _frame;
32+
private readonly BidiWorkerRealm _realm;
33+
34+
private BidiWebWorker(BidiFrame frame, DedicatedWorkerRealm realm) : base(realm.Origin)
3035
{
36+
_frame = frame;
37+
_realm = BidiWorkerRealm.From(realm, this);
38+
RealmId = realm.Id;
3139
}
3240

33-
public override ICDPSession Client { get; }
41+
public override ICDPSession Client => throw new NotSupportedException();
42+
43+
internal override IsolatedWorld World => throw new NotSupportedException();
44+
45+
internal BidiFrame Frame => _frame;
46+
47+
internal TimeoutSettings TimeoutSettings => _frame.TimeoutSettings;
48+
49+
internal string RealmId { get; }
3450

35-
internal override IsolatedWorld World { get; }
51+
public override Task CloseAsync()
52+
{
53+
// BiDi doesn't support closing workers directly.
54+
// Workers are closed when they terminate themselves or when the owning page/frame closes.
55+
// This matches the upstream Puppeteer behavior where close() throws UnsupportedOperation.
56+
throw new NotSupportedException("WebWorker.CloseAsync() is not supported in BiDi protocol");
57+
}
58+
59+
internal static BidiWebWorker From(BidiFrame frame, DedicatedWorkerRealm realm)
60+
{
61+
return new BidiWebWorker(frame, realm);
62+
}
3663

37-
public override Task CloseAsync() => throw new System.NotImplementedException();
64+
internal override Realm GetMainRealm() => _realm;
3865
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.Threading.Tasks;
24+
using PuppeteerSharp.Bidi.Core;
25+
using PuppeteerSharp.Helpers;
26+
27+
namespace PuppeteerSharp.Bidi;
28+
29+
internal class BidiWorkerRealm : BidiRealm
30+
{
31+
private readonly DedicatedWorkerRealm _realm;
32+
private readonly BidiWebWorker _worker;
33+
private readonly TaskQueue _puppeteerUtilQueue = new();
34+
private IJSHandle _puppeteerUtil;
35+
36+
private BidiWorkerRealm(DedicatedWorkerRealm realm, BidiWebWorker worker)
37+
: base(realm, worker.TimeoutSettings)
38+
{
39+
_realm = realm;
40+
_worker = worker;
41+
}
42+
43+
internal override IEnvironment Environment => _worker;
44+
45+
internal BidiWebWorker Worker => _worker;
46+
47+
public static BidiWorkerRealm From(DedicatedWorkerRealm realm, BidiWebWorker worker)
48+
{
49+
var workerRealm = new BidiWorkerRealm(realm, worker);
50+
workerRealm.Initialize();
51+
return workerRealm;
52+
}
53+
54+
public override async Task<IJSHandle> GetPuppeteerUtilAsync()
55+
{
56+
var scriptInjector = _realm.Session.ScriptInjector;
57+
58+
await _puppeteerUtilQueue.Enqueue(async () =>
59+
{
60+
if (_puppeteerUtil == null)
61+
{
62+
await scriptInjector.InjectAsync(
63+
async (script) =>
64+
{
65+
if (_puppeteerUtil != null)
66+
{
67+
await _puppeteerUtil.DisposeAsync().ConfigureAwait(false);
68+
}
69+
70+
_puppeteerUtil = await EvaluateExpressionHandleAsync(script).ConfigureAwait(false);
71+
},
72+
_puppeteerUtil == null).ConfigureAwait(false);
73+
}
74+
}).ConfigureAwait(false);
75+
76+
return _puppeteerUtil;
77+
}
78+
79+
protected override void Initialize()
80+
{
81+
base.Initialize();
82+
83+
_realm.Destroyed += (sender, args) => Dispose();
84+
_realm.Updated += (sender, args) =>
85+
{
86+
// Reset PuppeteerUtil when the realm is updated
87+
_puppeteerUtil = null;
88+
TaskManager.RerunAll();
89+
};
90+
}
91+
}

lib/PuppeteerSharp/Bidi/Core/DedicatedWorkerRealm.cs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,27 @@
2020
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
// * SOFTWARE.
2222

23+
using System;
24+
using System.Collections.Concurrent;
2325
using System.Linq;
2426
using PuppeteerSharp.Helpers;
27+
using WebDriverBiDi.Script;
2528

2629
namespace PuppeteerSharp.Bidi.Core;
2730

28-
internal class DedicatedWorkerRealm : Realm
31+
internal class DedicatedWorkerRealm : Realm, IDedicatedWorkerOwnerRealm
2932
{
3033
private readonly ConcurrentSet<IDedicatedWorkerOwnerRealm> _owners = [];
34+
private readonly ConcurrentDictionary<string, DedicatedWorkerRealm> _workers = [];
3135

3236
private DedicatedWorkerRealm(BrowsingContext context, IDedicatedWorkerOwnerRealm owner, string id, string origin)
3337
: base(context, id, origin)
3438
{
3539
_owners.Add(owner);
3640
}
3741

42+
public event EventHandler<WorkerRealmEventArgs> Worker;
43+
3844
public override Session Session => _owners.FirstOrDefault()?.Session;
3945

4046
public static DedicatedWorkerRealm From(BrowsingContext context, IDedicatedWorkerOwnerRealm owner, string id, string origin)
@@ -44,8 +50,54 @@ public static DedicatedWorkerRealm From(BrowsingContext context, IDedicatedWorke
4450
return realm;
4551
}
4652

53+
public override void Dispose()
54+
{
55+
foreach (var worker in _workers.Values)
56+
{
57+
worker.Dispose();
58+
}
59+
60+
base.Dispose();
61+
}
62+
4763
private void Initialize()
4864
{
49-
throw new System.NotImplementedException();
65+
// Listen to realm destruction
66+
Session.Driver.Script.OnRealmDestroyed.AddObserver(OnRealmDestroyed);
67+
68+
// Listen to nested worker creation
69+
Session.Driver.Script.OnRealmCreated.AddObserver(OnDedicatedRealmCreated);
5070
}
71+
72+
private void OnRealmDestroyed(RealmDestroyedEventArgs args)
73+
{
74+
if (args.RealmId != Id)
75+
{
76+
return;
77+
}
78+
79+
Dispose("Realm already destroyed.");
80+
}
81+
82+
private void OnDedicatedRealmCreated(RealmCreatedEventArgs args)
83+
{
84+
if (args.Type != RealmType.DedicatedWorker)
85+
{
86+
return;
87+
}
88+
89+
var dedicatedWorkerInfo = args.As<DedicatedWorkerRealmInfo>();
90+
91+
if (!dedicatedWorkerInfo.Owners.Contains(Id))
92+
{
93+
return;
94+
}
95+
96+
var realm = DedicatedWorkerRealm.From(Context, this, dedicatedWorkerInfo.RealmId, dedicatedWorkerInfo.Origin);
97+
_workers.TryAdd(realm.Id, realm);
98+
realm.Destroyed += (sender, args) => _workers.TryRemove(realm.Id, out _);
99+
OnWorker(realm);
100+
}
101+
102+
private void OnWorker(DedicatedWorkerRealm realm) => Worker?.Invoke(this, new WorkerRealmEventArgs(realm));
51103
}

lib/PuppeteerSharp/WebWorker.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ internal WebWorker(string url)
3838
public abstract ICDPSession Client { get; }
3939

4040
/// <inheritdoc/>
41-
Realm IEnvironment.MainRealm => World;
41+
Realm IEnvironment.MainRealm => GetMainRealm();
4242

4343
internal abstract IsolatedWorld World { get; }
4444

@@ -99,5 +99,7 @@ public async Task<IJSHandle> EvaluateExpressionHandleAsync(string script)
9999
/// </summary>
100100
/// <returns>A <see cref="Task"/> that completes when the worker is closed.</returns>
101101
public abstract Task CloseAsync();
102+
103+
internal virtual Realm GetMainRealm() => World;
102104
}
103105
}

0 commit comments

Comments
 (0)