Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement - Add DevToolsClient #3229

Merged
merged 25 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3154132
Core - Add DevToolsClient
amaitland Aug 26, 2020
edf71c8
DevTools Client - Add Network domain class
amaitland Sep 1, 2020
c841ecb
Devtools - First draft of devtools protocol methods (generated)
amaitland Sep 1, 2020
c0d253f
Devtools - Simple method execution working
amaitland Sep 4, 2020
ef0fdbf
DevTools - Add .Net Style cased property names to response objects
amaitland Sep 4, 2020
0146d2d
DevTools - Improve mapping
amaitland Sep 5, 2020
0fb761e
DevTools - Strongly typed enums
amaitland Sep 9, 2020
417fd5c
DevTools Client - Generated from active browser instance (M84)
amaitland Sep 11, 2020
020b111
Devtools Client - Extract IDevToolsClient interface for testability
amaitland Sep 11, 2020
4e4b624
DevTools Client - Improve error handling
amaitland Sep 11, 2020
d4e4344
DevTools Client - Improve enum conversion
amaitland Sep 11, 2020
e036551
Revert to older c# syntax
amaitland Sep 11, 2020
edd45d7
DevTools Client - Fix failing tests
amaitland Sep 12, 2020
b717085
Devtools Client - Add SetCookie Tests
amaitland Sep 12, 2020
201ba7b
DevTools Client - Add Capture SyncContext by default
amaitland Sep 12, 2020
6c18747
Devtools Client - Add missing file reference
amaitland Sep 12, 2020
6712e14
Devtools Client - Add CanCanEmulate Test case
amaitland Sep 14, 2020
b2f0dab
DevTools Client - Add AddScriptToEvaluateOnNewDocumentAsync test
amaitland Sep 18, 2020
24ff703
DevTools Client - OnDevToolsEvent only parse data if event handler !=…
amaitland Sep 18, 2020
eb34e61
DevTools Client - GetDevToolsClient remove ThrowExceptionIfDisposed/T…
amaitland Sep 18, 2020
065a97c
DevTools Client - Improve error handling
amaitland Sep 18, 2020
9f50947
DevTools Client - Update xml doc comments
amaitland Sep 19, 2020
304e4e0
DevTools Client - Add validation method (partial)
amaitland Sep 19, 2020
10c6d97
DevTools Client - Comment out failing test
amaitland Sep 19, 2020
f2a1fc4
OffScreen Example - Remove test code
amaitland Sep 19, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion CefSharp.Example/CefSharp.Example.csproj
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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.ResponseAsJsonString);

//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.

2 changes: 2 additions & 0 deletions CefSharp.Test/CefSharp.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="BindingRedirectAssemblyResolver.cs" />
<Compile Include="DevTools\DevToolsClientFacts.cs" />
<Compile Include="Framework\BinderFacts.cs" />
<Compile Include="Framework\AsyncExtensionFacts.cs" />
<Compile Include="Framework\ConcurrentMethodRunnerQueueFacts.cs" />
Expand All @@ -138,6 +139,7 @@
<Compile Include="CefSharpXunitTestFramework.cs" />
<Compile Include="CefSharpFixture.cs" />
<Compile Include="CefSharpFixtureCollection.cs" />
<Compile Include="PostMessage\IntegrationTestFacts.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WebBrowserTestExtensions.cs" />
<Compile Include="WinForms\WinFormsBrowserBasicFacts.cs" />
Expand Down
126 changes: 126 additions & 0 deletions CefSharp.Test/DevTools/DevToolsClientFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright © 2017 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.Threading.Tasks;
using CefSharp.DevTools.Browser;
using CefSharp.DevTools.Network;
using CefSharp.Example;
using CefSharp.OffScreen;
using Xunit;
using Xunit.Abstractions;

namespace CefSharp.Test.DevTools
{
//NOTE: All Test classes must be part of this collection as it manages the Cef Initialize/Shutdown lifecycle
[Collection(CefSharpFixtureCollection.Key)]
public class DevToolsClientFacts
{
private readonly ITestOutputHelper output;
private readonly CefSharpFixture fixture;

public DevToolsClientFacts(ITestOutputHelper output, CefSharpFixture fixture)
{
this.fixture = fixture;
this.output = output;
}

[Fact]
public void CanConvertDevToolsObjectToDictionary()
{
var bounds = new Bounds
{
Height = 1,
Width = 1,
Left = 1,
Top = 1,
WindowState = WindowState.Fullscreen
};

var dict = bounds.ToDictionary();

Assert.Equal(bounds.Height, (int)dict["height"]);
Assert.Equal(bounds.Width, (int)dict["width"]);
Assert.Equal(bounds.Top, (int)dict["top"]);
Assert.Equal(bounds.Left, (int)dict["left"]);
Assert.Equal("fullscreen", (string)dict["windowState"]);
}


[Fact]
public async Task CanGetDevToolsProtocolVersion()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadPageAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
var response = await devToolsClient.Browser.GetVersionAsync();
var jsVersion = response.JsVersion;
var revision = response.Revision;

Assert.NotNull(jsVersion);
Assert.NotNull(revision);

output.WriteLine("DevTools Revision {0}", revision);
}
}
}

[Fact]
public async Task CanCanEmulate()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadPageAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
var response = await devToolsClient.Emulation.CanEmulateAsync();

Assert.True(response.Result);
}
}
}

[Fact]
public async Task CanGetPageNavigationHistory()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadPageAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
var response = await devToolsClient.Page.GetNavigationHistoryAsync();
var currentIndex = response.CurrentIndex;
var entries = response.Entries;

Assert.Equal(0, currentIndex);
Assert.NotNull(entries);
Assert.True(entries.Count > 0);
Assert.Equal(CefSharp.DevTools.Page.TransitionType.Typed, entries[0].TransitionType);
}
}
}

[Theory]
//[InlineData("CefSharpTest", "CefSharp Test Cookie", CefExample.ExampleDomain, CookieSameSite.None)]
[InlineData("CefSharpTest1", "CefSharp Test Cookie2", CefExample.ExampleDomain, CookieSameSite.Lax)]
public async Task CanSetCookieForDomain(string name, string value, string domain, CookieSameSite sameSite)
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadPageAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
var response = await devToolsClient.Network.SetCookieAsync(name, value, domain: domain, sameSite: sameSite);
Assert.True(response.Success, "SetCookieForDomain");
}
}
}

}
}
79 changes: 79 additions & 0 deletions CefSharp.Test/PostMessage/IntegrationTestFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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.Threading.Tasks;
using CefSharp.OffScreen;
using CefSharp.Web;
using Xunit;
using Xunit.Abstractions;

namespace CefSharp.Test.PostMessage
{
/// <summary>
/// This is more a set of integration tests than it is unit tests, for now we need to
/// run our QUnit tests in an automated fashion and some other testing.
/// </summary>
//TODO: Improve Test Naming, we need a naming scheme that fits these cases that's consistent
//(Ideally we implement consistent naming accross all test classes, though I'm open to a different
//naming convention as these are more integration tests than unit tests).
//NOTE: All Test classes must be part of this collection as it manages the Cef Initialize/Shutdown lifecycle
[Collection(CefSharpFixtureCollection.Key)]
public class IntegrationTestFacts
{
private readonly ITestOutputHelper output;
private readonly CefSharpFixture fixture;

public IntegrationTestFacts(ITestOutputHelper output, CefSharpFixture fixture)
{
this.fixture = fixture;
this.output = output;
}

[Theory]
[InlineData("Event", "Event1")]
[InlineData("Event", "Event2")]
[InlineData("CustomEvent", "Event1")]
[InlineData("CustomEvent", "Event2")]
public async Task JavascriptCustomEvent(string jsEventObject, string eventToRaise)
{
const string Script = @"
const postMessageHandler = e => { cefSharp.postMessage(e.type); };
window.addEventListener(""Event1"", postMessageHandler, false);
window.addEventListener(""Event2"", postMessageHandler, false);";

string rawHtml = $"<html><head><script>window.dispatchEvent(new {jsEventObject}(\"{eventToRaise}\"));</script></head><body><h1>testing</h1></body></html>";
int scriptId = 0;

var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);

//Load a dummy page initially so we can then add our script using
//Page.AddScriptToEvaluateOnNewDocument (via DevTools)
using (var browser = new ChromiumWebBrowser(new HtmlString("Initial Load")))
{
await browser.LoadPageAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
var result = await devToolsClient.Page.AddScriptToEvaluateOnNewDocumentAsync(Script);
scriptId = int.Parse(result.Identifier);

//We must use Page.Enable for the script to be added
await devToolsClient.Page.EnableAsync();
}

browser.LoadHtml(rawHtml);

browser.JavascriptMessageReceived += (o, e) =>
{
tcs.SetResult((string)e.Message);
};

var responseFromJavascript = await tcs.Task;

Assert.True(scriptId > 0);
Assert.Equal(eventToRaise, responseFromJavascript);
}
}
}
}