Skip to content

Commit 2ce54b9

Browse files
committed
1 parent f3efcd9 commit 2ce54b9

File tree

1,935 files changed

+49389
-115
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,935 files changed

+49389
-115
lines changed

ChromeDevTools/CefChrome.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace MasterDevs.ChromeDevTools
8+
{
9+
public class CefChrome : RemoteChromeProcess
10+
{
11+
public CefChrome(Uri remoteDebuggingUri)
12+
: base(remoteDebuggingUri)
13+
{
14+
}
15+
}
16+
}

ChromeDevTools/CefFactory.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace MasterDevs.ChromeDevTools
8+
{
9+
public class CefFactory : IChromeProcessFactory
10+
{
11+
public IChromeProcess Create(int port, bool headless)
12+
{
13+
return new CefChrome(new Uri("http://localhost:" + port));
14+
}
15+
}
16+
}

ChromeDevTools/ChromeDevTools.csproj

Lines changed: 2059 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
5+
6+
namespace MasterDevs.ChromeDevTools
7+
{
8+
public class ChromeProcessFactory : IChromeProcessFactory
9+
{
10+
public IDirectoryCleaner DirectoryCleaner { get; set; }
11+
public string ChromePath { get; }
12+
13+
public ChromeProcessFactory(IDirectoryCleaner directoryCleaner, string chromePath = @"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe")
14+
{
15+
DirectoryCleaner = directoryCleaner;
16+
ChromePath = chromePath;
17+
}
18+
19+
public IChromeProcess Create(int port, bool headless)
20+
{
21+
string path = Path.GetRandomFileName();
22+
var directoryInfo = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), path));
23+
var remoteDebuggingArg = $"--remote-debugging-port={port}";
24+
var userDirectoryArg = $"--user-data-dir=\"{directoryInfo.FullName}\"";
25+
const string headlessArg = "--headless --disable-gpu";
26+
var chromeProcessArgs = new List<string>
27+
{
28+
remoteDebuggingArg,
29+
userDirectoryArg,
30+
"--bwsi",
31+
"--no-first-run"
32+
};
33+
if (headless)
34+
chromeProcessArgs.Add(headlessArg);
35+
var processStartInfo = new ProcessStartInfo(ChromePath, string.Join(" ", chromeProcessArgs));
36+
var chromeProcess = Process.Start(processStartInfo);
37+
38+
string remoteDebuggingUrl = "http://localhost:" + port;
39+
return new LocalChromeProcess(new Uri(remoteDebuggingUrl), () => DirectoryCleaner.Delete(directoryInfo), chromeProcess);
40+
}
41+
}
42+
}

ChromeDevTools/ChromeSession.cs

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
#if !NETSTANDARD1_5
2+
using MasterDevs.ChromeDevTools.Serialization;
3+
using Newtonsoft.Json;
4+
using System;
5+
using System.Collections.Concurrent;
6+
using System.Linq;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using WebSocket4Net;
10+
11+
namespace MasterDevs.ChromeDevTools
12+
{
13+
public class ChromeSession : IChromeSession
14+
{
15+
private readonly string _endpoint;
16+
private readonly ConcurrentDictionary<string, ConcurrentBag<Action<object>>> _handlers = new ConcurrentDictionary<string, ConcurrentBag<Action<object>>>();
17+
private ICommandFactory _commandFactory;
18+
private IEventFactory _eventFactory;
19+
private ManualResetEvent _openEvent = new ManualResetEvent(false);
20+
private ManualResetEvent _publishEvent = new ManualResetEvent(false);
21+
private ConcurrentDictionary<long, ManualResetEventSlim> _requestWaitHandles = new ConcurrentDictionary<long, ManualResetEventSlim>();
22+
private ICommandResponseFactory _responseFactory;
23+
private ConcurrentDictionary<long, ICommandResponse> _responses = new ConcurrentDictionary<long, ICommandResponse>();
24+
private WebSocket _webSocket;
25+
private static object _Lock = new object();
26+
27+
public event Action<string> UnknownMessageReceived;
28+
public event Action<byte[]> UnknownDataReceived;
29+
30+
public ChromeSession(string endpoint, ICommandFactory commandFactory, ICommandResponseFactory responseFactory, IEventFactory eventFactory)
31+
{
32+
_endpoint = endpoint;
33+
_commandFactory = commandFactory;
34+
_responseFactory = responseFactory;
35+
_eventFactory = eventFactory;
36+
}
37+
38+
public void Dispose()
39+
{
40+
if (null == _webSocket) return;
41+
if (_webSocket.State == WebSocketState.Open)
42+
{
43+
_webSocket.Close();
44+
}
45+
_webSocket.Dispose();
46+
}
47+
48+
private void EnsureInit()
49+
{
50+
if (null == _webSocket)
51+
{
52+
lock (_Lock)
53+
{
54+
if (null == _webSocket)
55+
{
56+
Init().Wait();
57+
}
58+
}
59+
}
60+
}
61+
62+
private Task Init()
63+
{
64+
_openEvent.Reset();
65+
66+
_webSocket = new WebSocket(_endpoint);
67+
_webSocket.EnableAutoSendPing = false;
68+
_webSocket.Opened += WebSocket_Opened;
69+
_webSocket.MessageReceived += WebSocket_MessageReceived;
70+
_webSocket.Error += WebSocket_Error;
71+
_webSocket.Closed += WebSocket_Closed;
72+
_webSocket.DataReceived += WebSocket_DataReceived;
73+
74+
_webSocket.Open();
75+
return Task.Run(() =>
76+
{
77+
_openEvent.WaitOne();
78+
});
79+
}
80+
81+
public Task<ICommandResponse> SendAsync<T>(CancellationToken cancellationToken)
82+
{
83+
var command = _commandFactory.Create<T>();
84+
return SendCommand(command, cancellationToken);
85+
}
86+
87+
public Task<CommandResponse<T>> SendAsync<T>(ICommand<T> parameter, CancellationToken cancellationToken)
88+
{
89+
var command = _commandFactory.Create(parameter);
90+
var task = SendCommand(command, cancellationToken);
91+
return CastTaskResult<ICommandResponse, CommandResponse<T>>(task);
92+
}
93+
94+
private Task<TDerived> CastTaskResult<TBase, TDerived>(Task<TBase> task) where TDerived: TBase, new()
95+
{
96+
var tcs = new TaskCompletionSource<TDerived>();
97+
task.ContinueWith(t => {
98+
if (t.Result is TDerived)
99+
tcs.SetResult((TDerived)t.Result);
100+
else
101+
tcs.SetResult(new TDerived());
102+
//if (t.Result is TDerived)
103+
// tcs.SetResult((TDerived)t.Result);
104+
}, TaskContinuationOptions.OnlyOnRanToCompletion);
105+
task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions),
106+
TaskContinuationOptions.OnlyOnFaulted);
107+
task.ContinueWith(t => tcs.SetCanceled(),
108+
TaskContinuationOptions.OnlyOnCanceled);
109+
return tcs.Task;
110+
}
111+
112+
public void Subscribe<T>(Action<T> handler) where T : class
113+
{
114+
var handlerType = typeof(T);
115+
var handlerForBag = new Action<object>(obj => handler((T)obj));
116+
_handlers.AddOrUpdate(handlerType.FullName,
117+
(m) => new ConcurrentBag<Action<object>>(new [] { handlerForBag }),
118+
(m, currentBag) =>
119+
{
120+
currentBag.Add(handlerForBag);
121+
return currentBag;
122+
});
123+
}
124+
125+
private void HandleEvent(IEvent evnt)
126+
{
127+
if (null == evnt
128+
|| null == evnt)
129+
{
130+
return;
131+
}
132+
var type = evnt.GetType().GetGenericArguments().FirstOrDefault();
133+
if (null == type)
134+
{
135+
return;
136+
}
137+
var handlerKey = type.FullName;
138+
ConcurrentBag<Action<object>> handlers = null;
139+
if (_handlers.TryGetValue(handlerKey, out handlers))
140+
{
141+
var localHandlers = handlers.ToArray();
142+
foreach (var handler in localHandlers)
143+
{
144+
ExecuteHandler(handler, evnt);
145+
}
146+
}
147+
}
148+
149+
private void ExecuteHandler(Action<object> handler, dynamic evnt)
150+
{
151+
if (evnt.GetType().GetGenericTypeDefinition() == typeof(Event<>))
152+
{
153+
handler(evnt.Params);
154+
} else
155+
{
156+
handler(evnt);
157+
}
158+
}
159+
160+
private void HandleResponse(ICommandResponse response)
161+
{
162+
if (null == response) return;
163+
ManualResetEventSlim requestMre;
164+
if (_requestWaitHandles.TryGetValue(response.Id, out requestMre))
165+
{
166+
_responses.AddOrUpdate(response.Id, id => response, (key, value) => response);
167+
requestMre.Set();
168+
}
169+
else
170+
{
171+
// in the case of an error, we don't always get the request Id back :(
172+
// if there is only one pending requests, we know what to do ... otherwise
173+
if (1 == _requestWaitHandles.Count)
174+
{
175+
var requestId = _requestWaitHandles.Keys.First();
176+
_requestWaitHandles.TryGetValue(requestId, out requestMre);
177+
_responses.AddOrUpdate(requestId, id => response, (key, value) => response);
178+
requestMre.Set();
179+
}
180+
}
181+
}
182+
183+
private Task<ICommandResponse> SendCommand(Command command, CancellationToken cancellationToken)
184+
{
185+
var settings = new JsonSerializerSettings
186+
{
187+
ContractResolver = new MessageContractResolver(),
188+
NullValueHandling = NullValueHandling.Ignore,
189+
};
190+
var requestString = JsonConvert.SerializeObject(command, settings);
191+
var requestResetEvent = new ManualResetEventSlim(false);
192+
_requestWaitHandles.AddOrUpdate(command.Id, requestResetEvent, (id, r) => requestResetEvent);
193+
return Task.Run(() =>
194+
{
195+
EnsureInit();
196+
_webSocket.Send(requestString);
197+
requestResetEvent.Wait(cancellationToken);
198+
ICommandResponse response = null;
199+
_responses.TryRemove(command.Id, out response);
200+
_requestWaitHandles.TryRemove(command.Id, out requestResetEvent);
201+
return response;
202+
});
203+
}
204+
205+
private bool TryGetCommandResponse(byte[] data, out ICommandResponse response)
206+
{
207+
response = _responseFactory.Create(data);
208+
return null != response;
209+
}
210+
211+
private bool TryGetCommandResponse(string message, out ICommandResponse response)
212+
{
213+
response = _responseFactory.Create(message);
214+
return null != response;
215+
}
216+
217+
private bool TryGetEvent(byte[] data, out IEvent evnt)
218+
{
219+
evnt = _eventFactory.Create(data);
220+
return null != evnt;
221+
}
222+
223+
private bool TryGetEvent(string message, out IEvent evnt)
224+
{
225+
evnt = _eventFactory.Create(message);
226+
return null != evnt;
227+
}
228+
229+
private void WebSocket_Closed(object sender, EventArgs e)
230+
{
231+
}
232+
233+
private void WebSocket_DataReceived(object sender, DataReceivedEventArgs e)
234+
{
235+
ICommandResponse response;
236+
if (TryGetCommandResponse(e.Data, out response))
237+
{
238+
HandleResponse(response);
239+
return;
240+
}
241+
IEvent evnt;
242+
if (TryGetEvent(e.Data, out evnt))
243+
{
244+
HandleEvent(evnt);
245+
return;
246+
}
247+
UnknownDataReceived?.Invoke(e.Data);
248+
}
249+
250+
private void WebSocket_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e)
251+
{
252+
throw e.Exception;
253+
}
254+
255+
private void WebSocket_MessageReceived(object sender, MessageReceivedEventArgs e)
256+
{
257+
ICommandResponse response;
258+
if (TryGetCommandResponse(e.Message, out response))
259+
{
260+
HandleResponse(response);
261+
return;
262+
}
263+
IEvent evnt;
264+
if (TryGetEvent(e.Message, out evnt))
265+
{
266+
HandleEvent(evnt);
267+
return;
268+
}
269+
UnknownMessageReceived?.Invoke(e.Message);
270+
}
271+
272+
private void WebSocket_Opened(object sender, EventArgs e)
273+
{
274+
_openEvent.Set();
275+
}
276+
}
277+
}
278+
#endif
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
4+
namespace MasterDevs.ChromeDevTools
5+
{
6+
public static class ChromeSessionExtensions
7+
{
8+
public static Task<CommandResponse<T>> SendAsync<T>(this IChromeSession session, ICommand<T> parameter)
9+
{
10+
return session.SendAsync(parameter, CancellationToken.None);
11+
}
12+
13+
public static Task<ICommandResponse> SendAsync<T>(this IChromeSession session)
14+
{
15+
return session.SendAsync<T>(CancellationToken.None);
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)