diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..1ff0c423042b46cb1d617b81efb715defbe8054d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/AutoWebPerf/App.config b/AutoWebPerf/App.config new file mode 100644 index 0000000000000000000000000000000000000000..8e15646352ec1d9a84bbc6504ef6b46e16bf7823 --- /dev/null +++ b/AutoWebPerf/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> + </startup> +</configuration> \ No newline at end of file diff --git a/AutoWebPerf/AutoWebPerf.csproj b/AutoWebPerf/AutoWebPerf.csproj new file mode 100644 index 0000000000000000000000000000000000000000..44b83369ebd73ba70f6d35c3022754d4fb42ba96 --- /dev/null +++ b/AutoWebPerf/AutoWebPerf.csproj @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{36138327-0A72-44E3-B9DB-C4E6155AAFD5}</ProjectGuid> + <OutputType>Exe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>AutoWebPerf</RootNamespace> + <AssemblyName>AutoWebPerf</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Newtonsoft.Json"> + <HintPath>packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Runtime.Serialization" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + <Reference Include="WebSocket4Net"> + <HintPath>packages\WebSocket4Net.0.12\lib\net45\WebSocket4Net.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="ChromeWrapper.cs" /> + <Compile Include="ChromeProcessInfo.cs" /> + <Compile Include="Chrome\IChromeProxy.cs" /> + <Compile Include="Chrome\MessageContractResolver.cs" /> + <Compile Include="Chrome\Messages\ChromeProxy.cs" /> + <Compile Include="Chrome\Messages\ErrorResponse.cs" /> + <Compile Include="Chrome\Messages\IdResponse.cs" /> + <Compile Include="Chrome\Messages\IError.cs" /> + <Compile Include="Chrome\Messages\IRequest.cs" /> + <Compile Include="Chrome\Messages\IRequestFactory.cs" /> + <Compile Include="Chrome\Messages\IResponse.cs" /> + <Compile Include="Chrome\Messages\IResponseFactory.cs" /> + <Compile Include="Chrome\Messages\MethodName.cs" /> + <Compile Include="Chrome\Messages\MethodNameAttribute.cs" /> + <Compile Include="Chrome\Messages\Network\DataReceivedResponse.cs" /> + <Compile Include="Chrome\Messages\Network\EnableRequest.cs" /> + <Compile Include="Chrome\Messages\Network\EnableResponse.cs" /> + <Compile Include="Chrome\Messages\Network\LoadingFinishedResponse.cs" /> + <Compile Include="Chrome\Messages\Network\RequestServedFromCacheResponse.cs" /> + <Compile Include="Chrome\Messages\Network\RequestWillBeSentResponse.cs" /> + <Compile Include="Chrome\Messages\Network\ResponseReceivedResponse.cs" /> + <Compile Include="Chrome\Messages\Page\NavigateRequest.cs" /> + <Compile Include="Chrome\Messages\Page\NavigateRequestParams.cs" /> + <Compile Include="Chrome\Messages\Page\NavigateResponse.cs" /> + <Compile Include="Chrome\Messages\Page\NavigateResponseResult.cs" /> + <Compile Include="Chrome\Messages\Request.cs" /> + <Compile Include="Chrome\Messages\RequestFactory.cs" /> + <Compile Include="Chrome\Messages\ResponseFactory.cs" /> + <Compile Include="Chrome\Messages\ResponseHandler.cs" /> + <Compile Include="Chrome\Messages\Response.cs" /> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="RemoveSessions.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/AutoWebPerf/AutoWebPerf.sln b/AutoWebPerf/AutoWebPerf.sln new file mode 100644 index 0000000000000000000000000000000000000000..423af3a79dbd27184d524ce889479f5bfb8fb0d4 --- /dev/null +++ b/AutoWebPerf/AutoWebPerf.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoWebPerf", "AutoWebPerf.csproj", "{36138327-0A72-44E3-B9DB-C4E6155AAFD5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {36138327-0A72-44E3-B9DB-C4E6155AAFD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36138327-0A72-44E3-B9DB-C4E6155AAFD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36138327-0A72-44E3-B9DB-C4E6155AAFD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36138327-0A72-44E3-B9DB-C4E6155AAFD5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoWebPerf/Chrome/IChromeProxy.cs b/AutoWebPerf/Chrome/IChromeProxy.cs new file mode 100644 index 0000000000000000000000000000000000000000..9052918e17db36984c5de4146edbf6289be0db41 --- /dev/null +++ b/AutoWebPerf/Chrome/IChromeProxy.cs @@ -0,0 +1,18 @@ +using AutoWebPerf.Chrome.Messages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome +{ + interface IChromeProxy : IDisposable + { + Task Init(); + + Task<IResponse> PublishAsync(IRequest request); + + void Subscribe<T>(ResponseHandler handler); + } +} diff --git a/AutoWebPerf/Chrome/MessageContractResolver.cs b/AutoWebPerf/Chrome/MessageContractResolver.cs new file mode 100644 index 0000000000000000000000000000000000000000..df78bd57f27aedddd186f86f0e226ea825998361 --- /dev/null +++ b/AutoWebPerf/Chrome/MessageContractResolver.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Serialization; + +namespace AutoWebPerf.Chrome +{ + class MessageContractResolver : DefaultContractResolver + { + protected override string ResolvePropertyName(string propertyName) + { + return propertyName.ToLower(); + } + } +} diff --git a/AutoWebPerf/Chrome/Messages/ChromeProxy.cs b/AutoWebPerf/Chrome/Messages/ChromeProxy.cs new file mode 100644 index 0000000000000000000000000000000000000000..7c05a9ed69ae74fb953fed33bdb83190489a47a8 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/ChromeProxy.cs @@ -0,0 +1,138 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WebSocket4Net; + +namespace AutoWebPerf.Chrome.Messages +{ + class ChromeProxy : IChromeProxy + { + private ManualResetEvent _openEvent = new ManualResetEvent(false); + private ManualResetEvent _publishEvent = new ManualResetEvent(false); + private readonly string _endpoint; + private WebSocket _webSocket; + private readonly ConcurrentDictionary<string, ConcurrentBag<ResponseHandler>> _handlers = new ConcurrentDictionary<string, ConcurrentBag<ResponseHandler>>(); + private IResponseFactory _responseFactory; + private ConcurrentDictionary<long, ManualResetEvent> _requestWaitHandles = new ConcurrentDictionary<long, ManualResetEvent>(); + private ConcurrentDictionary<long, IResponse> _responses = new ConcurrentDictionary<long, IResponse>(); + + public ChromeProxy(string endpoint, IResponseFactory responseFactory) + { + _endpoint = endpoint; + _responseFactory = responseFactory; + } + + public Task Init() + { + _openEvent.Reset(); + _webSocket = new WebSocket(_endpoint); + _webSocket.Opened += delegate(System.Object o, EventArgs e) + { + _openEvent.Set(); + }; + + _webSocket.MessageReceived += delegate(System.Object o, MessageReceivedEventArgs e) + { + var response = _responseFactory.Create(e.Message); + var idResponse = response as IdResponse; + ManualResetEvent requestMre = null; + long responseId; + if (null != idResponse + && long.TryParse(idResponse.Id, out responseId) + && _requestWaitHandles.TryGetValue(responseId, out requestMre)) + { + _responses.AddOrUpdate(responseId, response, (i, r) => response); + requestMre.Set(); + } + else + { + HandleResponse(response); + } + }; + + _webSocket.Error += delegate(System.Object o, SuperSocket.ClientEngine.ErrorEventArgs e) + { + throw e.Exception; + }; + + _webSocket.Closed += delegate(System.Object o, EventArgs e) + { + }; + + _webSocket.DataReceived += delegate(System.Object o, WebSocket4Net.DataReceivedEventArgs e) + { + var response = _responseFactory.Create(e.Data); + HandleResponse(response); + }; + + _webSocket.Open(); + return Task.Run(() => + { + _openEvent.WaitOne(); + }); + } + + private void HandleResponse(IResponse response) + { + var type = response.GetType(); + var handlerKey = type.FullName; + ConcurrentBag<ResponseHandler> handlers = null; + if(_handlers.TryGetValue(handlerKey, out handlers)) + { + var localHandlers = handlers.ToArray(); + foreach(var handler in localHandlers) + { + handler(this, response); + } + } + } + + public Task<IResponse> PublishAsync(IRequest request) + { + var settings = new JsonSerializerSettings + { + ContractResolver = new MessageContractResolver() + }; + var requestString = JsonConvert.SerializeObject(request, settings); + + var requestResetEvent = new ManualResetEvent(false); + _requestWaitHandles.AddOrUpdate(request.Id, requestResetEvent, (id, r) => requestResetEvent); + return Task.Run(() => + { + _webSocket.Send(requestString); + requestResetEvent.WaitOne(); + IResponse response = null; + _requestWaitHandles.TryRemove(request.Id, out requestResetEvent); + _responses.TryRemove(request.Id, out response); + return response; + }); + } + + public void Subscribe<T>(ResponseHandler handler) + { + var handlerType = typeof(T); + _handlers.AddOrUpdate(handlerType.FullName, + (m) => new ConcurrentBag<ResponseHandler>(new[] { handler }), + (m, currentBag) => + { + currentBag.Add(handler); + return currentBag; + }); + } + + public void Dispose() + { + if (null == _webSocket) return; + if (_webSocket.State == WebSocketState.Open) + { + _webSocket.Close(); + } + _webSocket.Dispose(); + } + } +} diff --git a/AutoWebPerf/Chrome/Messages/ErrorResponse.cs b/AutoWebPerf/Chrome/Messages/ErrorResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..ee121e592a5d269e3ec85e023888cf1b53c35e43 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/ErrorResponse.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + public class Error + { + public int Code { get; set; } + public string message { get; set; } + } + + class ErrorResponse : IdResponse + { + public Error Error { get; set; } + public int Id { get; set; } + } + + + + +} diff --git a/AutoWebPerf/Chrome/Messages/IError.cs b/AutoWebPerf/Chrome/Messages/IError.cs new file mode 100644 index 0000000000000000000000000000000000000000..2d91b4cfc6a0d1e04c1fd5f8f0bdbc6fd12b72df --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/IError.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + interface IError + { + } +} diff --git a/AutoWebPerf/Chrome/Messages/IRequest.cs b/AutoWebPerf/Chrome/Messages/IRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..e3620f422e78cbdb4c1dc73abefd4eef1cad09f4 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/IRequest.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + interface IRequest + { + long Id { get; } + + string Method { get; } + } + + interface IRequest<T> : IRequest + { + T Params { get; } + } +} diff --git a/AutoWebPerf/Chrome/Messages/IRequestFactory.cs b/AutoWebPerf/Chrome/Messages/IRequestFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..9d621ea521b8ff9e92d2f4fe840879f04c3a0d23 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/IRequestFactory.cs @@ -0,0 +1,19 @@ +using AutoWebPerf.Chrome.Messages.Page; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + interface IRequestFactory + { + string GetMethod(long id); + + + NavigateRequest CreateNavigateRequest(string url); + + Network.EnableRequest CreateNetworkEnableRequest(); + } +} diff --git a/AutoWebPerf/Chrome/Messages/IResponse.cs b/AutoWebPerf/Chrome/Messages/IResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..2890de12c7e02ff4f07878bbec24ea43e190f5f6 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/IResponse.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + interface IResponse + { + } + + interface IResponse<T> : IResponse + { + T Result { get; } + } +} diff --git a/AutoWebPerf/Chrome/Messages/IResponseFactory.cs b/AutoWebPerf/Chrome/Messages/IResponseFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..0fa26d26e99b9d13f42042f9ff03dfaee04942f7 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/IResponseFactory.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + interface IResponseFactory + { + IResponse Create(string responseText); + + IResponse Create(byte[] responseBytes); + } +} diff --git a/AutoWebPerf/Chrome/Messages/IdResponse.cs b/AutoWebPerf/Chrome/Messages/IdResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..a42f309258a5c054ff141d542c2102bda2a1779e --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/IdResponse.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + class IdResponse : Response + { + public string Id { get; set; } + } + + class IdResponse<T> : IdResponse, IResponse<T> + { + public T Result + { + get; + set; + } + } +} diff --git a/AutoWebPerf/Chrome/Messages/MethodName.cs b/AutoWebPerf/Chrome/Messages/MethodName.cs new file mode 100644 index 0000000000000000000000000000000000000000..8de1908031a4ceb87126412536ee62a6b310f7af --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/MethodName.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + public static class MethodName + { + public static class Network + { + public const string DataReceived = "Network.dataReceived"; + public const string Enable = "Network.enable"; + public const string LoadingFinished = "Network.loadingFinished"; + public const string RequestWillBeSent = "Network.requestWillBeSent"; + public const string ResponseReceived = "Network.responseReceived"; + public const string RequestServedFromCache = "Network.requestServedFromCache"; + } + public static class Page + { + public const string Navigate = "Page.navigate"; + } + } +} diff --git a/AutoWebPerf/Chrome/Messages/MethodNameAttribute.cs b/AutoWebPerf/Chrome/Messages/MethodNameAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..21688b3151da5a007726fc63912833bcbe188460 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/MethodNameAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace AutoWebPerf.Chrome.Messages +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class MethodNameAttribute : Attribute + { + public MethodNameAttribute(string methodName) + { + this.MethodName = methodName; + } + + public string MethodName { get; private set; } + } +} \ No newline at end of file diff --git a/AutoWebPerf/Chrome/Messages/Network/DataReceivedResponse.cs b/AutoWebPerf/Chrome/Messages/Network/DataReceivedResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..0be64d4674612c55b1169ac937970e1ef50477a1 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Network/DataReceivedResponse.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Network +{ + [MethodName(MethodName.Network.DataReceived)] + public class DataReceivedResponse : IResponse + { + public string method { get; set; } + public DataReceivedResponseParams Params { get; set; } + } + + public class DataReceivedResponseParams + { + public string requestId { get; set; } + public float timestamp { get; set; } + public int dataLength { get; set; } + public int encodedDataLength { get; set; } + } + +} diff --git a/AutoWebPerf/Chrome/Messages/Network/EnableRequest.cs b/AutoWebPerf/Chrome/Messages/Network/EnableRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..7f19a27ab1b0514d2bf0d8fd7de7f0800c03f073 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Network/EnableRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Network +{ + [MethodName(MethodName.Network.Enable)] + public class EnableRequest : Messages.Request + { + public EnableRequest(long id) + : base(id) + { + } + } +} diff --git a/AutoWebPerf/Chrome/Messages/Network/EnableResponse.cs b/AutoWebPerf/Chrome/Messages/Network/EnableResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..796ae0e43b586fd8c6500c74c7cc2c130374a404 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Network/EnableResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Network +{ + [MethodName(MethodName.Network.Enable)] + class EnableResponse : Messages.IdResponse + { + } +} diff --git a/AutoWebPerf/Chrome/Messages/Network/LoadingFinishedResponse.cs b/AutoWebPerf/Chrome/Messages/Network/LoadingFinishedResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..46f1f08975e3515d96cc93d6b9d34a862fd08d7d --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Network/LoadingFinishedResponse.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Network +{ + + [MethodName(MethodName.Network.LoadingFinished)] + public class LoadingFinishedResponse : IResponse + { + public string method { get; set; } + public LoadingFinishedResponseParams Params { get; set; } + } + + public class LoadingFinishedResponseParams + { + public string requestId { get; set; } + public float timestamp { get; set; } + public int encodedDataLength { get; set; } + } + +} diff --git a/AutoWebPerf/Chrome/Messages/Network/RequestServedFromCacheResponse.cs b/AutoWebPerf/Chrome/Messages/Network/RequestServedFromCacheResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..190c04c89a2ffcf4ac8af802d6bb0c230a9f6e08 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Network/RequestServedFromCacheResponse.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Network +{ + [MethodName(MethodName.Network.RequestServedFromCache)] + public class RequestServedFromCacheResponse : IResponse + { + public string method { get; set; } + public RequestServedFromCacheResponseParams Params { get; set; } + } + + public class RequestServedFromCacheResponseParams + { + public string requestId { get; set; } + } + +} diff --git a/AutoWebPerf/Chrome/Messages/Network/RequestWillBeSentResponse.cs b/AutoWebPerf/Chrome/Messages/Network/RequestWillBeSentResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..df0f237fecc2919ef40a34b81df9f3607d13bcba --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Network/RequestWillBeSentResponse.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Network +{ + [MethodName(MethodName.Network.RequestWillBeSent)] + public class RequestWillBeSentResponse : IResponse + { + public string method { get; set; } + public RequestWillBeSentResponseParams Params { get; set; } + } + + public class RequestWillBeSentResponseParams + { + public string requestId { get; set; } + public string frameId { get; set; } + public string loaderId { get; set; } + public string documentURL { get; set; } + public Request request { get; set; } + public float timestamp { get; set; } + public Initiator initiator { get; set; } + public string type { get; set; } + } + + public class Request + { + public string url { get; set; } + public string method { get; set; } + public RequestHeaders headers { get; set; } + } + + public class RequestHeaders + { + public string Accept { get; set; } + public string UserAgent { get; set; } + } + + public class Initiator + { + public string type { get; set; } + } + +} diff --git a/AutoWebPerf/Chrome/Messages/Network/ResponseReceivedResponse.cs b/AutoWebPerf/Chrome/Messages/Network/ResponseReceivedResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..de27258a680f6696ae23051ba9f87cd10f0f7036 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Network/ResponseReceivedResponse.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Network +{ + [MethodName(MethodName.Network.ResponseReceived)] + public class ResponseReceivedResponse : IResponse + { + public string method { get; set; } + public ResponseReceivedResponseParams Params { get; set; } + } + + public class ResponseReceivedResponseParams + { + public string requestId { get; set; } + public string frameId { get; set; } + public string loaderId { get; set; } + public float timestamp { get; set; } + public string type { get; set; } + public Response response { get; set; } + } + + public class Response + { + public string url { get; set; } + public int status { get; set; } + public string statusText { get; set; } + public ResponseHeaders headers { get; set; } + public string mimeType { get; set; } + public bool connectionReused { get; set; } + public int connectionId { get; set; } + public int encodedDataLength { get; set; } + public bool fromDiskCache { get; set; } + public bool fromServiceWorker { get; set; } + public Timing timing { get; set; } + public string headersText { get; set; } + public Requestheaders requestHeaders { get; set; } + public string requestHeadersText { get; set; } + public string remoteIPAddress { get; set; } + public int remotePort { get; set; } + public string protocol { get; set; } + } + + public class ResponseHeaders + { + public string Date { get; set; } + public string ContentEncoding { get; set; } + public string Server { get; set; } + public string XAspNetVersion { get; set; } + public string XPoweredBy { get; set; } + public string Vary { get; set; } + public string ContentType { get; set; } + public string accesscontrolalloworigin { get; set; } + public string CacheControl { get; set; } + public string SetCookie { get; set; } + public string accesscontrolallowheaders { get; set; } + public string ContentLength { get; set; } + public string Expires { get; set; } + } + + public class Timing + { + public float requestTime { get; set; } + public int proxyStart { get; set; } + public int proxyEnd { get; set; } + public int dnsStart { get; set; } + public int dnsEnd { get; set; } + public int connectStart { get; set; } + public int connectEnd { get; set; } + public int sslStart { get; set; } + public int sslEnd { get; set; } + public int serviceWorkerFetchStart { get; set; } + public int serviceWorkerFetchReady { get; set; } + public int serviceWorkerFetchEnd { get; set; } + public float sendStart { get; set; } + public float sendEnd { get; set; } + public float receiveHeadersEnd { get; set; } + } + + public class Requestheaders + { + public string Accept { get; set; } + public string Connection { get; set; } + public string AcceptEncoding { get; set; } + public string Host { get; set; } + public string AcceptLanguage { get; set; } + public string UserAgent { get; set; } + } + +} diff --git a/AutoWebPerf/Chrome/Messages/Page/NavigateRequest.cs b/AutoWebPerf/Chrome/Messages/Page/NavigateRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..bcdf05a55a4a06c72229c01b8b97f73dee056eaa --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Page/NavigateRequest.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Page +{ + [MethodName("Page.navigate")] + class NavigateRequest : Request<NavigateRequestParams> + { + public NavigateRequest(long id, NavigateRequestParams param) + : base(id, param) + { + + } + } +} diff --git a/AutoWebPerf/Chrome/Messages/Page/NavigateRequestParams.cs b/AutoWebPerf/Chrome/Messages/Page/NavigateRequestParams.cs new file mode 100644 index 0000000000000000000000000000000000000000..2227e0fbea1c1d6df71a0ab6a17341bbcf37df51 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Page/NavigateRequestParams.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Page +{ + class NavigateRequestParams + { + public string Url { get; set; } + } +} diff --git a/AutoWebPerf/Chrome/Messages/Page/NavigateResponse.cs b/AutoWebPerf/Chrome/Messages/Page/NavigateResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..6439585b90e5274f148ae703013df71cc3e25e4e --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Page/NavigateResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Page +{ + [MethodName(MethodName.Page.Navigate)] + class NavigateResponse : IdResponse<NavigateResponseResult> + { + } +} diff --git a/AutoWebPerf/Chrome/Messages/Page/NavigateResponseResult.cs b/AutoWebPerf/Chrome/Messages/Page/NavigateResponseResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..b0133b39305ca939d5043ef53fdfd7660be89470 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Page/NavigateResponseResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages.Page +{ + class NavigateResponseResult + { + public string FrameId { get; set; } + } +} diff --git a/AutoWebPerf/Chrome/Messages/Request.cs b/AutoWebPerf/Chrome/Messages/Request.cs new file mode 100644 index 0000000000000000000000000000000000000000..f1816dee39e1b3400ab313a231e6c4b41ff86085 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Request.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + public class Request : IRequest + { + public Request(long id) + { + Id = id; + Method = LookupMethodAttribute(); + } + + private string LookupMethodAttribute() + { + var methodNameAttribute = this.GetType().GetCustomAttributes(typeof(MethodNameAttribute), true) + .FirstOrDefault() as MethodNameAttribute; + if(null == methodNameAttribute) + { + throw new Exception("Request(id) constructor is called on an object without a [MethodName] attribute"); + } + return methodNameAttribute.MethodName; + } + + public Request(long id, string method) + { + Id = id; + Method = method; + } + + public long Id + { + get; + private set; + } + + public string Method + { + get; + private set; + } + } + + public class Request<T> : Request, IRequest<T> + { + public Request(long id, T methodParams) + : base(id) + { + Params = methodParams; + } + + public T Params + { + get; + private set; + } + } + +} diff --git a/AutoWebPerf/Chrome/Messages/RequestFactory.cs b/AutoWebPerf/Chrome/Messages/RequestFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..fa33724e60b4671a9659e245ebcd3a908df7f2f5 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/RequestFactory.cs @@ -0,0 +1,47 @@ +using AutoWebPerf.Chrome.Messages.Page; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace AutoWebPerf.Chrome.Messages +{ + internal class RequestFactory : IRequestFactory + { + private readonly ConcurrentDictionary<long, string> _methods = new ConcurrentDictionary<long, string>(); + private long _count = 0; + public NavigateRequest CreateNavigateRequest(string url) + { + return CreateRequest(id => + { + var requestParams = new NavigateRequestParams + { + Url = url + }; + return new NavigateRequest(id, requestParams); + }); + } + + public Network.EnableRequest CreateNetworkEnableRequest() + { + return CreateRequest(id => new Network.EnableRequest(id)); + } + + public string GetMethod(long id) + { + return _methods[id]; + } + + private T CreateRequest<T>(Func<long, T> factory) where T : IRequest + { + var requestId = GetNextRequestId(); + var request = factory(requestId); + _methods.AddOrUpdate(request.Id, request.Method, (id, m) => request.Method); + return request; + } + + private long GetNextRequestId() + { + return Interlocked.Increment(ref _count); + } + } +} \ No newline at end of file diff --git a/AutoWebPerf/Chrome/Messages/Response.cs b/AutoWebPerf/Chrome/Messages/Response.cs new file mode 100644 index 0000000000000000000000000000000000000000..eb1a2015982d34d53b969d503a2d644edd39e640 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/Response.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + class Response : IResponse + { + } + class Response<T> : IResponse<T> + { + public T Result + { + get; + set; + } + } + +} diff --git a/AutoWebPerf/Chrome/Messages/ResponseFactory.cs b/AutoWebPerf/Chrome/Messages/ResponseFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..6543a7c944af89c78eedb06cac440e7ece832896 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/ResponseFactory.cs @@ -0,0 +1,94 @@ +using AutoWebPerf.Chrome.Messages.Network; +using AutoWebPerf.Chrome.Messages.Page; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + class ResponseFactory : IResponseFactory + { + private readonly static Lazy<Dictionary<string, Type>> _methodTypeMapLazy = new Lazy<Dictionary<string, Type>>(LoadMethodTypeMap); + + private static Dictionary<string, Type> LoadMethodTypeMap() + { + var iresponseType = typeof(IResponse); + var assembly = Assembly.GetExecutingAssembly(); + var assemblyTypes = assembly.GetTypes(); + var responseTypes = assemblyTypes.Where(t => iresponseType.IsAssignableFrom(t) && t.IsClass); + Dictionary<string, Type> result = new Dictionary<string, Type>(); + foreach(var responseType in responseTypes) + { + var methodNameAttribute = responseType.GetCustomAttribute<MethodNameAttribute>(false) as MethodNameAttribute; + if (null == methodNameAttribute) continue; + result[methodNameAttribute.MethodName] = responseType; + } + return result; + } + + private readonly IRequestFactory _requestFactory; + + public ResponseFactory(IRequestFactory requestFactory) + { + _requestFactory = requestFactory; + } + + public IResponse Create(string responseText) + { + var jObject = JObject.Parse(responseText); + if(null != jObject["error"]) + { + return jObject.ToObject<ErrorResponse>(); + } + var response = ResponseFromMethodResponse(jObject); + if (null != response) return response; + response = ResponseFromMethodLookupResponse(jObject); + if (null != response) return response; + throw new Exception("We could not create a response from text: [" + responseText + "]"); + } + + private IResponse ResponseFromMethodLookupResponse(JObject jObject) + { + var resultObj = jObject["result"]; + if (null == resultObj) return null; + var resultIdObj = jObject["id"]; + if (null == resultIdObj) return null; + long resultId; + if (!long.TryParse(resultIdObj.ToString(), out resultId)) return null; + var methodLookupResult = _requestFactory.GetMethod(resultId); + if(String.IsNullOrEmpty(methodLookupResult)) return null; + return ResponseFromMethodResponse(methodLookupResult, jObject); + } + + private IResponse ResponseFromMethodResponse(JObject jObject) + { + var method = jObject["method"]; + if (null == method) return null; + return ResponseFromMethodResponse(method.ToString(), jObject); + } + + private IResponse ResponseFromMethodResponse(string methodName, JObject jObject) + { + if(String.IsNullOrEmpty(methodName)) return null; + Type responseType = null; + if (!_methodTypeMapLazy.Value.TryGetValue(methodName, out responseType)) + { + throw new ArgumentException("We could not find a response type for the method " + methodName); + } + var response = jObject.ToObject(responseType) as IResponse; + return response; + } + + + public IResponse Create(byte[] responseBytes) + { + var responseString = Encoding.Default.GetString(responseBytes); + return Create(responseString); + } + } +} diff --git a/AutoWebPerf/Chrome/Messages/ResponseHandler.cs b/AutoWebPerf/Chrome/Messages/ResponseHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..5142c08aeeec19f0485519c6a61e697b8d184a48 --- /dev/null +++ b/AutoWebPerf/Chrome/Messages/ResponseHandler.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf.Chrome.Messages +{ + delegate void ResponseHandler(object sender, IResponse response); +} diff --git a/AutoWebPerf/ChromeProcessInfo.cs b/AutoWebPerf/ChromeProcessInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..34cdf77f7455c630b50dba58444bb81a1589499c --- /dev/null +++ b/AutoWebPerf/ChromeProcessInfo.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf +{ + class ChromeProcessInfo + { + public DirectoryInfo ChromeUserDirectory { get; set; } + public Process ChromeProcess { get; set; } + } +} diff --git a/AutoWebPerf/ChromeWrapper.cs b/AutoWebPerf/ChromeWrapper.cs new file mode 100644 index 0000000000000000000000000000000000000000..4cf010df1569f1c4bf746d1fcc792aae7fb0e5cd --- /dev/null +++ b/AutoWebPerf/ChromeWrapper.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading.Tasks; +using System.Net.WebSockets; +using System.Threading; +using WebSocket4Net; + +namespace AutoWebPerf +{ + /// <summary> + /// ABPS: https://markcz.wordpress.com/2012/02/18/automating-chrome-browser-from-csharp/ + /// </summary> + public class ChromeWrapper + { + const string JsonPostfix = "/json"; + + string remoteDebuggingUri; + string sessionWSEndpoint; + + public ChromeWrapper(string remoteDebuggingUri) + { + this.remoteDebuggingUri = remoteDebuggingUri; + } + + public TRes SendRequest<TRes>() + { + HttpWebRequest req = (HttpWebRequest)WebRequest.Create(remoteDebuggingUri + JsonPostfix); + var resp = req.GetResponse(); + var respStream = resp.GetResponseStream(); + + StreamReader sr = new StreamReader(respStream); + var s = sr.ReadToEnd(); + resp.Dispose(); + return Deserialise<TRes>(s); + } + + public List<RemoteSessionsResponse> GetAvailableSessions() + { + var res = this.SendRequest<List<RemoteSessionsResponse>>(); + return (from r in res + where r.devtoolsFrontendUrl != null + select r).ToList(); + } + + public string NavigateTo(string uri) + { + // Page.navigate is working from M18 + var json = @"{""method"":""Page.navigate"",""params"":{""url"":""" + uri + @"""},""id"":1}"; + + // Instead of Page.navigate, we can use document.location + //var json = @"{""method"":""Runtime.evaluate"",""params"":{""expression"":""document.location='" + uri + @"'"",""objectGroup"":""console"",""includeCommandLineAPI"":true,""doNotPauseOnExceptions"":false,""returnByValue"":false},""id"":1}"; + return this.SendCommand(json); + } + + public string GetElementsByTagName(string tagName) + { + // Page.navigate is working from M18 + //var json = @"{""method"":""Page.navigate"",""params"":{""url"":""http://www.seznam.cz""},""id"":1}"; + + // Instead of Page.navigate, we can use document.location + var json = @"{""method"":""Runtime.evaluate"",""params"":{""expression"":""document.getElementsByTagName('" + tagName + @"')"",""objectGroup"":""console"",""includeCommandLineAPI"":true,""doNotPauseOnExceptions"":false,""returnByValue"":false},""id"":1}"; + return this.SendCommand(json); + } + + + public string Eval(string cmd) + { + var json = @"{""method"":""Runtime.evaluate"",""params"":{""expression"":""" + cmd + @""",""objectGroup"":""console"",""includeCommandLineAPI"":true,""doNotPauseOnExceptions"":false,""returnByValue"":false},""id"":1}"; + return this.SendCommand(json); + } + + public string SendCommand(string cmd) + { + WebSocket4Net.WebSocket j = new WebSocket4Net.WebSocket(this.sessionWSEndpoint); + ManualResetEvent waitEvent = new ManualResetEvent(false); + ManualResetEvent closedEvent = new ManualResetEvent(false); + string message = ""; + byte[] data; + + Exception exc = null; + j.Opened += delegate(System.Object o, EventArgs e) + { + j.Send(cmd); + }; + + j.MessageReceived += delegate(System.Object o, MessageReceivedEventArgs e) + { + message = e.Message; + waitEvent.Set(); + }; + + j.Error += delegate(System.Object o, SuperSocket.ClientEngine.ErrorEventArgs e) + { + exc = e.Exception; + waitEvent.Set(); + }; + + j.Closed += delegate(System.Object o, EventArgs e) + { + closedEvent.Set(); + }; + + j.DataReceived += delegate(System.Object o, DataReceivedEventArgs e) + { + data = e.Data; + waitEvent.Set(); + }; + + j.Open(); + + waitEvent.WaitOne(); + if (j.State == WebSocket4Net.WebSocketState.Open) + { + j.Close(); + closedEvent.WaitOne(); + } + if (exc != null) + throw exc; + + return message; + } + + private T Deserialise<T>(string json) + { + + T obj = Activator.CreateInstance<T>(); + using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json))) + { + DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType()); + obj = (T)serializer.ReadObject(ms); + return obj; + } + } + + private T Deserialise<T>(Stream json) + { + T obj = Activator.CreateInstance<T>(); + DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType()); + obj = (T)serializer.ReadObject(json); + return obj; + } + + public void SetActiveSession(string sessionWSEndpoint) + { + // Sometimes binding to localhost might resolve wrong AddressFamily, force IPv4 + this.sessionWSEndpoint = sessionWSEndpoint.Replace("ws://localhost", "ws://127.0.0.1"); + + } + } +} \ No newline at end of file diff --git a/AutoWebPerf/Program.cs b/AutoWebPerf/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..94509113471909affdb964ce00f63a5c76e35bb7 --- /dev/null +++ b/AutoWebPerf/Program.cs @@ -0,0 +1,104 @@ +using AutoWebPerf.Chrome; +using AutoWebPerf.Chrome.Messages; +using AutoWebPerf.Chrome.Messages.Network; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WebSocket4Net; + +namespace AutoWebPerf +{ + class Program + { + static void Main(string[] args) + { + IChromeProxy _chromeProxy = null; + ChromeProcessInfo chromeProcessInfo = null; + + try + { + chromeProcessInfo = StartChrome(); + + var c = new ChromeWrapper("http://localhost:" + ChromeRemoteDebuggingPort); + var sessions = c.GetAvailableSessions(); + foreach (var s in sessions) + Console.WriteLine(s.url); + + if (sessions.Count == 0) + throw new Exception("All debugging sessions are taken."); + + + + // Will drive first tab session + var sessionWSEndpoint = sessions.Last().webSocketDebuggerUrl; // new tab window + c.SetActiveSession(sessionWSEndpoint); + c.NavigateTo("http://www.google.com"); + + + var requestFactory = new RequestFactory(); + _chromeProxy = new ChromeProxy(sessionWSEndpoint, new ResponseFactory(requestFactory)); + _chromeProxy.Init().Wait(); + var result = _chromeProxy.PublishAsync(requestFactory.CreateNetworkEnableRequest()).Result; + _chromeProxy.Subscribe<RequestWillBeSentResponse>((o, e) => + { + Console.WriteLine("We got it!"); + }); + _chromeProxy.PublishAsync(requestFactory.CreateNavigateRequest("http://www.google.com")).Wait(); + + + Console.ReadLine(); + } + finally + { + if(null != _chromeProxy) + { + _chromeProxy.Dispose(); + } + KillChrome(chromeProcessInfo); + } + + } + + private static void KillChrome(ChromeProcessInfo chromeProcessInfo) + { + try + { + chromeProcessInfo.ChromeProcess.Kill(); + } catch + { + // right now, the process has been killed ... so i'm swallowing this during setup/testing + } + try + { + chromeProcessInfo.ChromeUserDirectory.Delete(true); + } catch + { + Thread.Sleep(500); + chromeProcessInfo.ChromeUserDirectory.Delete(true); + } + } + + private static ChromeProcessInfo StartChrome() + { + string path = Path.GetRandomFileName(); + var directoryInfo = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), path)); + var remoteDebuggingArg = "--remote-debugging-port=" + ChromeRemoteDebuggingPort; + var userDirectoryArg = "--user-data-dir=\"" + directoryInfo.FullName + "\""; + var chromeProcessArgs = remoteDebuggingArg + " " + userDirectoryArg + " --bwsi --no-first-run"; + var processStartInfo = new ProcessStartInfo(@"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", chromeProcessArgs); + var chromeProcess = Process.Start(processStartInfo); + return new ChromeProcessInfo + { + ChromeProcess = chromeProcess, + ChromeUserDirectory = directoryInfo + }; + } + + public const int ChromeRemoteDebuggingPort = 9222; + } +} diff --git a/AutoWebPerf/Properties/AssemblyInfo.cs b/AutoWebPerf/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..8f94ac81291492f939cb821efc3f47a066c40383 --- /dev/null +++ b/AutoWebPerf/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoWebPerf")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AutoWebPerf")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d4a99e75-3de5-4de5-8bb9-00659c480504")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoWebPerf/RemoveSessions.cs b/AutoWebPerf/RemoveSessions.cs new file mode 100644 index 0000000000000000000000000000000000000000..f51f32ff6b157e74b3432639ca8934fb09943f31 --- /dev/null +++ b/AutoWebPerf/RemoveSessions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace AutoWebPerf +{ + [Serializable] + [DataContract] + public class RemoteSessionsResponse + { + public RemoteSessionsResponse() { } + + [DataMember] + public string devtoolsFrontendUrl; + + [DataMember] + public string faviconUrl; + + [DataMember] + public string thumbnailUrl; + + [DataMember] + public string title; + + [DataMember] + public string url; + + [DataMember] + public string webSocketDebuggerUrl; + } +} diff --git a/AutoWebPerf/packages.config b/AutoWebPerf/packages.config new file mode 100644 index 0000000000000000000000000000000000000000..afd4ccc71de3168b8272ba3b01b94c7c5044cff8 --- /dev/null +++ b/AutoWebPerf/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" /> + <package id="WebSocket4Net" version="0.12" targetFramework="net45" /> +</packages> \ No newline at end of file