diff --git a/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.local.json b/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.local.json index 7f04c154b..9342ca36a 100644 --- a/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.local.json +++ b/lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.local.json @@ -1312,21 +1312,6 @@ "FAIL" ] }, - { - "comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one", - "testIdPattern": "[worker.spec] *", - "platforms": [ - "darwin", - "linux", - "win32" - ], - "parameters": [ - "webDriverBiDi" - ], - "expectations": [ - "FAIL" - ] - }, { "comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one", "testIdPattern": "[ChromeLauncher.test.ts] *", diff --git a/lib/PuppeteerSharp/Bidi/BidiFrame.cs b/lib/PuppeteerSharp/Bidi/BidiFrame.cs index a9f429492..85b97aec8 100644 --- a/lib/PuppeteerSharp/Bidi/BidiFrame.cs +++ b/lib/PuppeteerSharp/Bidi/BidiFrame.cs @@ -604,6 +604,20 @@ private void Initialize() BidiPage.OnPageError(new PageErrorEventArgs(fullStack)); } }; + + // Wire up worker events + if (_realms.Default is BidiFrameRealm defaultFrameRealm) + { + defaultFrameRealm.WindowRealm.Worker += (sender, args) => + { + var worker = BidiWebWorker.From(this, args.Realm); + args.Realm.Destroyed += (o, eventArgs) => + { + BidiPage.OnWorkerDestroyed(worker); + }; + BidiPage.OnWorkerCreated(worker); + }; + } } private void CreateFrameTarget(BrowsingContext browsingContext) diff --git a/lib/PuppeteerSharp/Bidi/BidiFrameRealm.cs b/lib/PuppeteerSharp/Bidi/BidiFrameRealm.cs index 7e56a3fba..71b8dd6a9 100644 --- a/lib/PuppeteerSharp/Bidi/BidiFrameRealm.cs +++ b/lib/PuppeteerSharp/Bidi/BidiFrameRealm.cs @@ -35,6 +35,8 @@ internal class BidiFrameRealm(WindowRealm realm, BidiFrame frame) : BidiRealm(re internal BidiFrame Frame => frame; + internal WindowRealm WindowRealm => _realm; + public static BidiFrameRealm From(WindowRealm realm, BidiFrame frame) { var frameRealm = new BidiFrameRealm(realm, frame); diff --git a/lib/PuppeteerSharp/Bidi/BidiWebWorker.cs b/lib/PuppeteerSharp/Bidi/BidiWebWorker.cs index 6962f7368..f9effdf67 100644 --- a/lib/PuppeteerSharp/Bidi/BidiWebWorker.cs +++ b/lib/PuppeteerSharp/Bidi/BidiWebWorker.cs @@ -20,19 +20,42 @@ // * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // * SOFTWARE. +using System; using System.Threading.Tasks; +using PuppeteerSharp.Bidi.Core; namespace PuppeteerSharp.Bidi; internal class BidiWebWorker : WebWorker { - public BidiWebWorker(string url) : base(url) + private readonly BidiFrame _frame; + private readonly BidiWorkerRealm _realm; + + private BidiWebWorker(BidiFrame frame, DedicatedWorkerRealm realm) : base(realm.Origin) + { + _frame = frame; + _realm = BidiWorkerRealm.From(realm, this); + } + + public override ICDPSession Client => throw new NotSupportedException(); + + public override Task CloseAsync() { + // BiDi doesn't support closing workers directly + // Workers are closed when they terminate themselves or when the owning page/frame closes + throw new NotSupportedException("WebWorker.CloseAsync() is not supported in BiDi protocol"); } - public override ICDPSession Client { get; } + internal static BidiWebWorker From(BidiFrame frame, DedicatedWorkerRealm realm) + { + return new BidiWebWorker(frame, realm); + } + + internal override IsolatedWorld World => throw new NotSupportedException(); + + internal override Realm GetMainRealm() => _realm; - internal override IsolatedWorld World { get; } + internal BidiFrame Frame => _frame; - public override Task CloseAsync() => throw new System.NotImplementedException(); + internal TimeoutSettings TimeoutSettings => _frame.TimeoutSettings; } diff --git a/lib/PuppeteerSharp/Bidi/BidiWorkerRealm.cs b/lib/PuppeteerSharp/Bidi/BidiWorkerRealm.cs new file mode 100644 index 000000000..fc2f21eea --- /dev/null +++ b/lib/PuppeteerSharp/Bidi/BidiWorkerRealm.cs @@ -0,0 +1,91 @@ +// * MIT License +// * +// * Copyright (c) DarĂ­o Kondratiuk +// * +// * Permission is hereby granted, free of charge, to any person obtaining a copy +// * of this software and associated documentation files (the "Software"), to deal +// * in the Software without restriction, including without limitation the rights +// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// * copies of the Software, and to permit persons to whom the Software is +// * furnished to do so, subject to the following conditions: +// * +// * The above copyright notice and this permission notice shall be included in all +// * copies or substantial portions of the Software. +// * +// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// * SOFTWARE. + +using System.Threading.Tasks; +using PuppeteerSharp.Bidi.Core; +using PuppeteerSharp.Helpers; + +namespace PuppeteerSharp.Bidi; + +internal class BidiWorkerRealm : BidiRealm +{ + private readonly DedicatedWorkerRealm _realm; + private readonly BidiWebWorker _worker; + private readonly TaskQueue _puppeteerUtilQueue = new(); + private IJSHandle _puppeteerUtil; + + private BidiWorkerRealm(DedicatedWorkerRealm realm, BidiWebWorker worker) + : base(realm, worker.TimeoutSettings) + { + _realm = realm; + _worker = worker; + } + + internal override IEnvironment Environment => _worker; + + internal BidiWebWorker Worker => _worker; + + public static BidiWorkerRealm From(DedicatedWorkerRealm realm, BidiWebWorker worker) + { + var workerRealm = new BidiWorkerRealm(realm, worker); + workerRealm.Initialize(); + return workerRealm; + } + + public override async Task GetPuppeteerUtilAsync() + { + var scriptInjector = _realm.Session.ScriptInjector; + + await _puppeteerUtilQueue.Enqueue(async () => + { + if (_puppeteerUtil == null) + { + await scriptInjector.InjectAsync( + async (script) => + { + if (_puppeteerUtil != null) + { + await _puppeteerUtil.DisposeAsync().ConfigureAwait(false); + } + + _puppeteerUtil = await EvaluateExpressionHandleAsync(script).ConfigureAwait(false); + }, + _puppeteerUtil == null).ConfigureAwait(false); + } + }).ConfigureAwait(false); + + return _puppeteerUtil; + } + + protected override void Initialize() + { + base.Initialize(); + + _realm.Destroyed += (sender, args) => Dispose(); + _realm.Updated += (sender, args) => + { + // Reset PuppeteerUtil when the realm is updated + _puppeteerUtil = null; + TaskManager.RerunAll(); + }; + } +} diff --git a/lib/PuppeteerSharp/Bidi/Core/DedicatedWorkerRealm.cs b/lib/PuppeteerSharp/Bidi/Core/DedicatedWorkerRealm.cs index 1ccae26cd..ae1360ea4 100644 --- a/lib/PuppeteerSharp/Bidi/Core/DedicatedWorkerRealm.cs +++ b/lib/PuppeteerSharp/Bidi/Core/DedicatedWorkerRealm.cs @@ -20,14 +20,18 @@ // * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // * SOFTWARE. +using System; +using System.Collections.Concurrent; using System.Linq; using PuppeteerSharp.Helpers; +using WebDriverBiDi.Script; namespace PuppeteerSharp.Bidi.Core; -internal class DedicatedWorkerRealm : Realm +internal class DedicatedWorkerRealm : Realm, IDedicatedWorkerOwnerRealm { private readonly ConcurrentSet _owners = []; + private readonly ConcurrentDictionary _workers = []; private DedicatedWorkerRealm(BrowsingContext context, IDedicatedWorkerOwnerRealm owner, string id, string origin) : base(context, id, origin) @@ -35,6 +39,8 @@ private DedicatedWorkerRealm(BrowsingContext context, IDedicatedWorkerOwnerRealm _owners.Add(owner); } + public event EventHandler Worker; + public override Session Session => _owners.FirstOrDefault()?.Session; public static DedicatedWorkerRealm From(BrowsingContext context, IDedicatedWorkerOwnerRealm owner, string id, string origin) @@ -44,8 +50,54 @@ public static DedicatedWorkerRealm From(BrowsingContext context, IDedicatedWorke return realm; } + public override void Dispose() + { + foreach (var worker in _workers.Values) + { + worker.Dispose(); + } + + base.Dispose(); + } + private void Initialize() { - throw new System.NotImplementedException(); + // Listen to realm destruction + Session.Driver.Script.OnRealmDestroyed.AddObserver(OnRealmDestroyed); + + // Listen to nested worker creation + Session.Driver.Script.OnRealmCreated.AddObserver(OnDedicatedRealmCreated); } + + private void OnRealmDestroyed(RealmDestroyedEventArgs args) + { + if (args.RealmId != Id) + { + return; + } + + Dispose("Realm already destroyed."); + } + + private void OnDedicatedRealmCreated(RealmCreatedEventArgs args) + { + if (args.Type != RealmType.DedicatedWorker) + { + return; + } + + var dedicatedWorkerInfo = args.As(); + + if (!dedicatedWorkerInfo.Owners.Contains(Id)) + { + return; + } + + var realm = DedicatedWorkerRealm.From(Context, this, dedicatedWorkerInfo.RealmId, dedicatedWorkerInfo.Origin); + _workers.TryAdd(realm.Id, realm); + realm.Destroyed += (sender, args) => _workers.TryRemove(realm.Id, out _); + OnWorker(realm); + } + + private void OnWorker(DedicatedWorkerRealm realm) => Worker?.Invoke(this, new WorkerRealmEventArgs(realm)); } diff --git a/lib/PuppeteerSharp/WebWorker.cs b/lib/PuppeteerSharp/WebWorker.cs index ce34d5b99..f4aad2832 100644 --- a/lib/PuppeteerSharp/WebWorker.cs +++ b/lib/PuppeteerSharp/WebWorker.cs @@ -37,11 +37,6 @@ internal WebWorker(string url) /// public abstract ICDPSession Client { get; } - /// - Realm IEnvironment.MainRealm => World; - - internal abstract IsolatedWorld World { get; } - /// /// Executes a script in browser context. /// @@ -99,5 +94,12 @@ public async Task EvaluateExpressionHandleAsync(string script) /// /// A that completes when the worker is closed. public abstract Task CloseAsync(); + + internal abstract IsolatedWorld World { get; } + + internal virtual Realm GetMainRealm() => World; + + /// + Realm IEnvironment.MainRealm => GetMainRealm(); } }