Skip to content

Commit

Permalink
Core - Add DevToolsClient
Browse files Browse the repository at this point in the history
  • Loading branch information
amaitland committed Aug 26, 2020
1 parent b0b5103 commit d535d4c
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 107 deletions.
1 change: 0 additions & 1 deletion CefSharp.Example/CefSharp.Example.csproj
Expand Up @@ -76,7 +76,6 @@
<ItemGroup>
<Compile Include="Callback\RunFileDialogCallback.cs" />
<Compile Include="DevTools\DevToolsExtensions.cs" />
<Compile Include="DevTools\TaskMethodDevToolsMessageObserver.cs" />
<Compile Include="Handlers\AudioHandler.cs" />
<Compile Include="Handlers\ExampleResourceRequestHandler.cs" />
<Compile Include="Handlers\ExtensionHandler.cs" />
Expand Down
46 changes: 7 additions & 39 deletions CefSharp.Example/DevTools/DevToolsExtensions.cs
@@ -1,14 +1,11 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace CefSharp.Example.DevTools
{
public static class DevToolsExtensions
{
private static int LastMessageId = 600000;
/// <summary>
/// Calls Page.captureScreenshot without any optional params
/// (Results in PNG image of default viewport)
Expand All @@ -23,58 +20,29 @@ public static async Task<byte[]> CaptureScreenShotAsPng(this IWebBrowser chromiu
// throw new System.Exception("Page hasn't loaded");
//}

var host = chromiumWebBrowser.GetBrowserHost();
var browser = chromiumWebBrowser.GetBrowser();

if (host == null || host.IsDisposed)
if (browser == null || browser.IsDisposed)
{
throw new Exception("BrowserHost is Null or Disposed");
throw new Exception("browser is Null or Disposed");
}

//var param = new Dictionary<string, object>
//{
// { "format", "png" },
//}

var msgId = Interlocked.Increment(ref LastMessageId);

var observer = new TaskMethodDevToolsMessageObserver(msgId);

//Make sure to dispose of our observer registration when done
//TODO: Create a single observer that maps tasks to Id's
//Or at least create one for each type, events and method
using (var observerRegistration = host.AddDevToolsMessageObserver(observer))
using (var devToolsClient = browser.GetDevToolsClient())
{
//Page.captureScreenshot defaults to PNG, all params are optional
//for this DevTools method
int id = 0;
const string methodName = "Page.captureScreenshot";

//TODO: Simplify this, we can use an Func to reduce code duplication
if (Cef.CurrentlyOnThread(CefThreadIds.TID_UI))
{
id = host.ExecuteDevToolsMethod(msgId, methodName);
}
else
{
id = await Cef.UIThreadTaskFactory.StartNew(() =>
{
return host.ExecuteDevToolsMethod(msgId, methodName);
});
}

if (id != msgId)
{
throw new Exception("Message Id doesn't match the provided Id");
}

var result = await observer.Task;

var success = result.Item1;
var result = await devToolsClient.ExecuteDevToolsMethodAsync(methodName);

dynamic response = JsonConvert.DeserializeObject<dynamic>(Encoding.UTF8.GetString(result.Item2));
dynamic response = JsonConvert.DeserializeObject<dynamic>(result.ResultAsJsonString);

//Success
if (success)
if (result.Success)
{
return Convert.FromBase64String((string)response.data);
}
Expand Down
67 changes: 0 additions & 67 deletions CefSharp.Example/DevTools/TaskMethodDevToolsMessageObserver.cs

This file was deleted.

3 changes: 3 additions & 0 deletions CefSharp/CefSharp.csproj
Expand Up @@ -104,6 +104,9 @@
<Compile Include="CdmRegistration.cs" />
<Compile Include="DefaultApp.cs" />
<Compile Include="DevToolsExtensions.cs" />
<Compile Include="DevTools\DevToolsClient.cs" />
<Compile Include="DevTools\DevToolsEventArgs.cs" />
<Compile Include="DevTools\DevToolsMethodResult.cs" />
<Compile Include="Enums\CompositionUnderlineStyle.cs" />
<Compile Include="Enums\SchemeOptions.cs" />
<Compile Include="Internals\CefThread.cs" />
Expand Down
164 changes: 164 additions & 0 deletions CefSharp/DevTools/DevToolsClient.cs
@@ -0,0 +1,164 @@
// Copyright © 2020 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CefSharp.Callback;
using CefSharp.Internals;

namespace CefSharp.DevTools
{
/// <summary>
/// DevToolClient
/// </summary>
public class DevToolsClient : IDevToolsMessageObserver
{
private readonly ConcurrentDictionary<int, TaskCompletionSource<DevToolsMethodResult>> queuedCommandResults = new ConcurrentDictionary<int, TaskCompletionSource<DevToolsMethodResult>>();
private int lastMessageId;
private IBrowser browser;
private IRegistration devToolsRegistration;
private bool devToolsAttached;

/// <summary>
/// DevToolsEvent
/// </summary>
public EventHandler<DevToolsEventArgs> DevToolsEvent;

/// <summary>
/// DevToolsClient
/// </summary>
/// <param name="browser">Browser associated with this DevTools client</param>
public DevToolsClient(IBrowser browser)
{
this.browser = browser;

lastMessageId = browser.Identifier * 100000;
}

public void SetDevToolsObserverRegistration(IRegistration devToolsRegistration)
{
this.devToolsRegistration = devToolsRegistration;
}

/// <summary>
/// Execute a method call over the DevTools protocol. This method can be called on any thread.
/// See the DevTools protocol documentation at https://chromedevtools.github.io/devtools-protocol/ for details
/// of supported methods and the expected <paramref name="parameters"/> dictionary contents.
/// </summary>
/// <param name="method">is the method name</param>
/// <param name="parameters">are the method parameters represented as a dictionary,
/// which may be empty.</param>
/// <returns>return a Task that can be awaited to obtain the method result</returns>
public async Task<DevToolsMethodResult> ExecuteDevToolsMethodAsync(string method, IDictionary<string, object> parameters = null)
{
if (browser == null || browser.IsDisposed)
{
//TODO: Queue up commands where possible
return new DevToolsMethodResult { Success = false };
}

var messageId = Interlocked.Increment(ref lastMessageId);

var taskCompletionSource = new TaskCompletionSource<DevToolsMethodResult>();

if (!queuedCommandResults.TryAdd(messageId, taskCompletionSource))
{
return new DevToolsMethodResult { Success = false };
}

var browserHost = browser.GetHost();

if (CefThread.CurrentlyOnUiThread)
{
var returnedMessageId = browserHost.ExecuteDevToolsMethod(messageId, method, parameters);
if (returnedMessageId == 0)
{
return new DevToolsMethodResult { Success = false };
}
}

if (CefThread.CanExecuteOnUiThread)
{
var returnedMessageId = await CefThread.ExecuteOnUiThread(() =>
{
return browserHost.ExecuteDevToolsMethod(messageId, method, parameters);
}).ConfigureAwait(false);

if (returnedMessageId == 0)
{
return new DevToolsMethodResult { Success = false };
}
}

return await taskCompletionSource.Task;
}

void IDisposable.Dispose()
{
devToolsRegistration?.Dispose();
devToolsRegistration = null;
browser = null;
}

void IDevToolsMessageObserver.OnDevToolsAgentAttached(IBrowser browser)
{
devToolsAttached = true;
}

void IDevToolsMessageObserver.OnDevToolsAgentDetached(IBrowser browser)
{
devToolsAttached = false;
}

void IDevToolsMessageObserver.OnDevToolsEvent(IBrowser browser, string method, Stream parameters)
{
//TODO: Improve this
var memoryStream = new MemoryStream((int)parameters.Length);
parameters.CopyTo(memoryStream);

var paramsAsJsonString = Encoding.UTF8.GetString(memoryStream.ToArray());

DevToolsEvent?.Invoke(this, new DevToolsEventArgs(method, paramsAsJsonString));
}

bool IDevToolsMessageObserver.OnDevToolsMessage(IBrowser browser, Stream message)
{
return false;
}

void IDevToolsMessageObserver.OnDevToolsMethodResult(IBrowser browser, int messageId, bool success, Stream result)
{
TaskCompletionSource<DevToolsMethodResult> taskCompletionSource = null;

if (queuedCommandResults.TryRemove(messageId, out taskCompletionSource))
{
var methodResult = new DevToolsMethodResult
{
Success = success
};

if (success)
{
//TODO: Improve this
var memoryStream = new MemoryStream((int)result.Length);

result.CopyTo(memoryStream);

methodResult.ResultAsJsonString = Encoding.UTF8.GetString(memoryStream.ToArray());
}

Task.Run(() =>
{
//Make sure continuation runs on Thread Pool
taskCompletionSource.TrySetResult(methodResult);
});
}
}
}
}
31 changes: 31 additions & 0 deletions CefSharp/DevTools/DevToolsEventArgs.cs
@@ -0,0 +1,31 @@
// Copyright © 2020 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.


using System;

namespace CefSharp.DevTools
{
/// <summary>
/// DevTools Event EventAargs
/// </summary>
public class DevToolsEventArgs : EventArgs
{
/// <summary>
/// Method
/// </summary>
public string EventName { get; private set; }

/// <summary>
/// Event paramaters as Json string
/// </summary>
public string ParametersAsJsonString { get; private set; }

public DevToolsEventArgs(string eventName, string paramsAsJsonString)
{
EventName = eventName;
ParametersAsJsonString = paramsAsJsonString;
}
}
}
28 changes: 28 additions & 0 deletions CefSharp/DevTools/DevToolsMethodResult.cs
@@ -0,0 +1,28 @@
// Copyright © 2020 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

namespace CefSharp.DevTools
{
/// <summary>
/// DevTools Method Result
/// </summary>
public class DevToolsMethodResult
{
/// <summary>
/// MessageId
/// </summary>
public int MessageId { get; set; }

/// <summary>
/// Success
/// </summary>
public bool Success { get; set; }

/// <summary>
/// Method Result as Json string
/// </summary>
public string ResultAsJsonString { get; set; }

}
}

0 comments on commit d535d4c

Please sign in to comment.