Skip to content

Commit dc757a4

Browse files
authored
Refactor global bingins (#2343)
* Refactor global bingins * fix typo * Improve comment * Make injected source static * arrows * Queue utils registration * Add close reason
1 parent 26520ea commit dc757a4

File tree

16 files changed

+650
-542
lines changed

16 files changed

+650
-542
lines changed

lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public async Task ShouldBeAbleToDetachSession()
8383
Assert.AreEqual(3, evalResponse["result"]["value"].ToObject<int>());
8484
await client.DetachAsync();
8585

86-
var exception = Assert.ThrowsAsync<PuppeteerException>(()
86+
var exception = Assert.ThrowsAsync<TargetClosedException>(()
8787
=> client.SendAsync("Runtime.evaluate", new RuntimeEvaluateRequest
8888
{
8989
Expression = "3 + 1",

lib/PuppeteerSharp.Tests/WontImplementTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public WontImplementTests(): base()
3434
[PuppeteerTest("EventEmitter.spec.ts", "removeAllListeners", "removes every listener from all events by default")]
3535
[PuppeteerTest("EventEmitter.spec.ts", "removeAllListeners", "returns the emitter for chaining")]
3636
[PuppeteerTest("EventEmitter.spec.ts", "removeAllListeners", "can filter to remove only listeners for a given event name")]
37+
[PuppeteerTest("elementhandle.spec.ts", "Custom queries", "should work with function shorthands")]
3738
[PuppeteerTest("emulation.spec.ts", "Page.emulateMediaType", "should throw in case of bad argument")]
3839
[PuppeteerTest("emulation.spec.ts", "Page.emulateMediaFeatures", "should throw in case of bad argument")]
3940
[PuppeteerTest("emulation.spec.ts", "Page.emulateVisionDeficiency", "should throw for invalid vision deficiencies")]

lib/PuppeteerSharp/Browser.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,6 @@ public bool IsClosed
136136

137137
internal LauncherBase Launcher { get; set; }
138138

139-
internal CustomQueriesManager CustomQueriesManager { get; } = new();
140-
141139
internal ITargetManager TargetManager { get; }
142140

143141
internal Func<Target, bool> IsPageTargetFunc { get; set; }
@@ -245,16 +243,16 @@ public void RegisterCustomQueryHandler(string name, CustomQueryHandler queryHand
245243
throw new ArgumentNullException(nameof(queryHandler));
246244
}
247245

248-
CustomQueriesManager.RegisterCustomQueryHandler(name, queryHandler);
246+
Connection.CustomQuerySelectorRegistry.RegisterCustomQueryHandler(name, queryHandler);
249247
}
250248

251249
/// <inheritdoc/>
252250
public void UnregisterCustomQueryHandler(string name)
253-
=> CustomQueriesManager.UnregisterCustomQueryHandler(name);
251+
=> Connection.CustomQuerySelectorRegistry.UnregisterCustomQueryHandler(name);
254252

255253
/// <inheritdoc/>
256254
public void ClearCustomQueryHandlers()
257-
=> CustomQueriesManager.ClearCustomQueryHandlers();
255+
=> Connection.CustomQuerySelectorRegistry.ClearCustomQueryHandlers();
258256

259257
/// <inheritdoc />
260258
public void Dispose()
@@ -311,7 +309,7 @@ internal static async Task<Browser> CreateAsync(
311309
}
312310

313311
internal IEnumerable<string> GetCustomQueryHandlerNames()
314-
=> CustomQueriesManager.GetCustomQueryHandlerNames();
312+
=> Connection.CustomQuerySelectorRegistry.GetCustomQueryHandlerNames();
315313

316314
internal async Task<IPage> CreatePageInContextAsync(string contextId)
317315
{

lib/PuppeteerSharp/CDPSession.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ public async Task<JObject> SendAsync(string method, object args = null, bool wai
6464
{
6565
if (Connection == null)
6666
{
67-
throw new PuppeteerException(
67+
throw new TargetClosedException(
6868
$"Protocol error ({method}): Session closed. " +
6969
$"Most likely the {TargetType} has been closed." +
70-
$"Close reason: {CloseReason}");
70+
$"Close reason: {CloseReason}",
71+
CloseReason);
7172
}
7273

7374
var id = Connection.GetMessageID();

lib/PuppeteerSharp/Connection.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using PuppeteerSharp.Helpers;
1111
using PuppeteerSharp.Helpers.Json;
1212
using PuppeteerSharp.Messaging;
13+
using PuppeteerSharp.QueryHandlers;
1314
using PuppeteerSharp.Transport;
1415

1516
namespace PuppeteerSharp
@@ -93,6 +94,13 @@ internal Connection(string url, int delay, bool enqueueAsyncMessages, IConnectio
9394

9495
internal AsyncMessageQueue MessageQueue { get; }
9596

97+
// The connection is a good place to keep the state of custom queries and injectors.
98+
// Although I consider that the Browser class would be a better place for this,
99+
// The connection is being shared between all the components involved in one browser instance
100+
internal CustomQuerySelectorRegistry CustomQuerySelectorRegistry { get; } = new();
101+
102+
internal ScriptInjector ScriptInjector { get; } = new();
103+
96104
/// <inheritdoc />
97105
public void Dispose()
98106
{

lib/PuppeteerSharp/QueryHandlers/CustomQueryHandler.cs renamed to lib/PuppeteerSharp/CustomQueryHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace PuppeteerSharp.QueryHandlers
1+
namespace PuppeteerSharp
22
{
33
// This class represents both InternalQueryHandler and CustomQueryHandler upstream.
44

lib/PuppeteerSharp/ElementHandle.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ internal ElementHandle(
4343

4444
internal Frame Frame { get; }
4545

46-
internal CustomQueriesManager CustomQueriesManager => Page.Browser.CustomQueriesManager;
46+
internal CustomQuerySelectorRegistry CustomQuerySelectorRegistry => Client.Connection.CustomQuerySelectorRegistry;
4747

4848
private string DebuggerDisplay =>
4949
string.IsNullOrEmpty(RemoteObject.ClassName) ? ToString() : $"{RemoteObject.ClassName}@{RemoteObject.Description}";
@@ -90,8 +90,7 @@ public async Task<IElementHandle> WaitForSelectorAsync(string selector, WaitForS
9090
throw new ArgumentNullException(nameof(selector));
9191
}
9292

93-
var customQueriesManager = Frame.FrameManager.Page.Browser.CustomQueriesManager;
94-
var (updatedSelector, queryHandler) = customQueriesManager.GetQueryHandlerAndSelector(selector);
93+
var (updatedSelector, queryHandler) = CustomQuerySelectorRegistry.GetQueryHandlerAndSelector(selector);
9594
return await queryHandler.WaitForAsync(null, this, updatedSelector, options).ConfigureAwait(false);
9695
}
9796

@@ -261,7 +260,7 @@ public Task<IElementHandle> QuerySelectorAsync(string selector)
261260
throw new ArgumentNullException(nameof(selector));
262261
}
263262

264-
var (updatedSelector, queryHandler) = CustomQueriesManager.GetQueryHandlerAndSelector(selector);
263+
var (updatedSelector, queryHandler) = CustomQuerySelectorRegistry.GetQueryHandlerAndSelector(selector);
265264
return queryHandler.QueryOneAsync(this, updatedSelector);
266265
}
267266

@@ -273,7 +272,7 @@ public async Task<IElementHandle[]> QuerySelectorAllAsync(string selector)
273272
throw new ArgumentNullException(nameof(selector));
274273
}
275274

276-
var (updatedSelector, queryHandler) = CustomQueriesManager.GetQueryHandlerAndSelector(selector);
275+
var (updatedSelector, queryHandler) = CustomQuerySelectorRegistry.GetQueryHandlerAndSelector(selector);
277276
var result = new List<IElementHandle>();
278277
await foreach (var item in queryHandler.QueryAllAsync(this, updatedSelector))
279278
{

lib/PuppeteerSharp/ExecutionContext.cs

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Newtonsoft.Json.Linq;
1010
using PuppeteerSharp.Helpers;
1111
using PuppeteerSharp.Messaging;
12+
using PuppeteerSharp.QueryHandlers;
1213

1314
namespace PuppeteerSharp
1415
{
@@ -18,9 +19,9 @@ public class ExecutionContext : IExecutionContext
1819
internal const string EvaluationScriptUrl = "__puppeteer_evaluation_script__";
1920

2021
private static readonly Regex _sourceUrlRegex = new(@"^[\040\t]*\/\/[@#] sourceURL=\s*\S*?\s*$", RegexOptions.Multiline);
21-
private static string _injectedSource;
2222

2323
private readonly string _evaluationScriptSuffix = $"//# sourceURL={EvaluationScriptUrl}";
24+
private readonly TaskQueue _puppeteerUtilQueue = new();
2425
private IJSHandle _puppeteerUtil;
2526

2627
internal ExecutionContext(
@@ -98,12 +99,27 @@ public async Task<IJSHandle> QueryObjectsAsync(IJSHandle prototypeHandle)
9899

99100
internal async Task<IJSHandle> GetPuppeteerUtilAsync()
100101
{
101-
if (_puppeteerUtil == null)
102+
await _puppeteerUtilQueue.Enqueue(async () =>
102103
{
103-
var injectedSource = GetInjectedSource();
104-
_puppeteerUtil = await EvaluateExpressionHandleAsync(injectedSource).ConfigureAwait(false);
105-
}
106-
104+
if (_puppeteerUtil == null)
105+
{
106+
await Client.Connection.ScriptInjector.InjectAsync(
107+
async (string script) =>
108+
{
109+
if (_puppeteerUtil != null)
110+
{
111+
await _puppeteerUtil.DisposeAsync().ConfigureAwait(false);
112+
}
113+
114+
await InstallGlobalBindingAsync(new Binding(
115+
"__ariaQuerySelector",
116+
(Func<IElementHandle, string, Task<IElementHandle>>)Client.Connection.CustomQuerySelectorRegistry.InternalQueryHandlers["aria"].QueryOneAsync))
117+
.ConfigureAwait(false);
118+
_puppeteerUtil = await EvaluateExpressionHandleAsync(script).ConfigureAwait(false);
119+
},
120+
_puppeteerUtil == null).ConfigureAwait(false);
121+
}
122+
}).ConfigureAwait(false);
107123
return _puppeteerUtil;
108124
}
109125

@@ -138,22 +154,6 @@ internal async Task<IElementHandle> AdoptElementHandleAsync(IElementHandle eleme
138154
return CreateJSHandle(obj.Object) as ElementHandle;
139155
}
140156

141-
private static string GetInjectedSource()
142-
{
143-
if (string.IsNullOrEmpty(_injectedSource))
144-
{
145-
var assembly = Assembly.GetExecutingAssembly();
146-
var resourceName = "PuppeteerSharp.Injected.injected.js";
147-
148-
using var stream = assembly.GetManifestResourceStream(resourceName);
149-
using var reader = new StreamReader(stream);
150-
var fileContent = reader.ReadToEnd();
151-
_injectedSource = fileContent;
152-
}
153-
154-
return _injectedSource;
155-
}
156-
157157
private static string GetExceptionMessage(EvaluateExceptionResponseDetails exceptionDetails)
158158
{
159159
if (exceptionDetails.Exception != null)
@@ -175,6 +175,24 @@ private static string GetExceptionMessage(EvaluateExceptionResponseDetails excep
175175
return message;
176176
}
177177

178+
private async Task InstallGlobalBindingAsync(Binding binding)
179+
{
180+
try
181+
{
182+
if (World != null)
183+
{
184+
World.Bindings.TryAdd(binding.Name, binding);
185+
await World.AddBindingToContextAsync(this, binding.Name).ConfigureAwait(false);
186+
}
187+
}
188+
catch
189+
{
190+
// If the binding cannot be added, then either the browser doesn't support
191+
// bindings (e.g. Firefox) or the context is broken. Either breakage is
192+
// okay, so we ignore the error.
193+
}
194+
}
195+
178196
private async Task<T> RemoteObjectTaskToObject<T>(Task<RemoteObject> remote)
179197
{
180198
var response = await remote.ConfigureAwait(false);

lib/PuppeteerSharp/Frame.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ public async Task<IElementHandle> WaitForSelectorAsync(string selector, WaitForS
102102
throw new ArgumentNullException(nameof(selector));
103103
}
104104

105-
var customQueriesManager = ((Browser)FrameManager.Page.Browser).CustomQueriesManager;
106-
var (updatedSelector, queryHandler) = customQueriesManager.GetQueryHandlerAndSelector(selector);
105+
var (updatedSelector, queryHandler) = Client.Connection.CustomQuerySelectorRegistry.GetQueryHandlerAndSelector(selector);
107106
return await queryHandler.WaitForAsync(this, null, updatedSelector, options).ConfigureAwait(false);
108107
}
109108

0 commit comments

Comments
 (0)