├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── chrome-dev-tools-generator.sln └── src ├── BaristaLabs.ChromeDevTools.Core ├── BaristaLabs.ChromeDevTools.Core.csproj ├── Chrome.cs └── ChromeVersion.cs ├── BaristaLabs.ChromeDevTools.RemoteInterface ├── BaristaLabs.ChromeDevTools.RemoteInterface.csproj ├── CodeGen │ ├── CodeGenerationDefinitionTemplateSettings.cs │ ├── CodeGenerationSettings.cs │ ├── CodeGenerationTemplateSettings.cs │ ├── CodeGeneratorBase.cs │ ├── CodeGeneratorContext.cs │ ├── CommandGenerator.cs │ ├── CommandInfo.cs │ ├── DomainGenerator.cs │ ├── EventGenerator.cs │ ├── EventInfo.cs │ ├── ICodeGenerator.cs │ ├── IServiceProviderExtensions.cs │ ├── ProtocolGenerator.cs │ ├── TemplatesManager.cs │ ├── TypeGenerator.cs │ ├── TypeInfo.cs │ └── Utility.cs ├── Converters │ └── BooleanJsonConverter.cs └── ProtocolDefinition │ ├── CommandDefinition.cs │ ├── DomainDefinition.cs │ ├── EventDefinition.cs │ ├── IDefinition.cs │ ├── ProtocolDefinition.cs │ ├── ProtocolDefinitionItem.cs │ ├── TypeDefinition.cs │ └── Version.cs └── ChromeDevToolsGeneratorCLI ├── ChromeDevToolsGeneratorCLI.csproj ├── CliArguments.cs ├── PdlConverter.cs ├── Program.cs ├── Properties └── launchSettings.json └── Templates ├── ChromeSession.hbs ├── ChromeSession_Domains.hbs ├── CommandResponseException.hbs ├── CommandResponseExtensions.hbs ├── CommandResponseTypeMap.hbs ├── EventTypeMap.hbs ├── ICommand.hbs ├── IEvent.hbs ├── command.hbs ├── domain.hbs ├── event.hbs ├── project.hbs ├── settings.json ├── type-enum.hbs ├── type-hash.hbs └── type-object.hbs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | #other stuffs 264 | src/ChromeDevToolsGeneratorCLI/OutputProtocol/ 265 | src/ChromeDevToolsGeneratorCLI/chrome-debugging-protocol.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 BaristaLabs, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chrome-dev-tools-generator 2 | This project generates a wrapper based on the current version of the Chrome Developer Protocol Definition. The supplied template targets .Net Core and creates a .csproj and corresponding class files that provide the connection and a strongly-typed interface to allow one to develop applictions that use the debugger protocol for fun and profit. 3 | 4 | # Why use the Chrome Debugger Protocol? 5 | 6 | Some use cases for using the Chrome Debugger Protocol to automate Chrome are: 7 | - Information Gathering - Use the web as a data source by creating structured data out of unstructured data 8 | - Testing - Ensure sites are up and behave according to a pre-determined notion 9 | - Automation - Do things like perform automated password resets, enter online contests, notify when sites change, etc... 10 | - Artifact Generation - Use Chrome as a tool to generate PDFs, Images, json/xml/txt/csv/... from web and locally supplied data sources 11 | - Debugging JavaScript - When implementing custom editors, allow for rich debugging of executing javascript, including setting breakpoints, getting immediate values when paused, stepping through and so forth. 12 | 13 | The protocol itself is defined by Google via a JSON-based definition of a websocket interface, allowing for tools to generate the required interface implementation in the language of choice. 14 | 15 | A number of other Browsers have started implementing the Chrome Debugger Protocol, including Edge, Safari (iOS), Firefox and possibly more, which makes developing a common interface to one of these browsers highly attractive. 16 | 17 | # Why a generator instead of a concrete implementation or library? 18 | 19 | The protocol is changing rapidly and some have varying opinions on what the wrapper library should look like, by rerunning this generator one can easily update a wrapper to the latest protocol definition. 20 | 21 | Also, while this project has a prebuilt template targeting .Net, it's also possible to customize the template to generate wrappers in other languages. 22 | 23 | If you just don't care and want a chrome developer tools runtime library for C#/dotnet, grab the runtime library from [this repository](http://github.com/BaristaLabs/chrome-dev-tools-sample). 24 | 25 | The runtime library is also available on nuget: 26 | 27 | https://www.nuget.org/packages/BaristaLabs.ChromeDevTools.Runtime/ 28 | 29 | ## Requirements: 30 | 31 | - Chrome 32 | - .Net Core 2.1.0 33 | 34 | ## Usage: 35 | 36 | Clone the project and run ChromeDevToolsGeneratorCLI 37 | 38 | ``` Bash 39 | $ git clone https://github.com/BaristaLabs/chrome-dev-tools 40 | $ cd chrome-dev-tools\src\ChromeDevToolsGeneratorCLI 41 | $ dotnet run ChromeDevToolsGeneratorCLI -o "C:\\temp\\ChromeDevToolsRuntime" 42 | ``` 43 | 44 | In C:\Temp\ChromeDevToolsRuntime you'll have a .csproj that you can build directly or include as part of a solution. 45 | 46 | > See [http://github.com/BaristaLabs/chrome-dev-tools-sample](http://github.com/BaristaLabs/chrome-dev-tools-sample) for a sample of generated output usage. 47 | 48 | ## Command line options: 49 | 50 | Option | Short | Description 51 | --------------- | --- | --- 52 | ```-output-path ``` | ```-o``` | Specifies the path to where the .csproj and code files will be generated (Usually contained in a subfolder of a solution that will utilize it) [Default: ./OutputProtocol] 53 | ```-force``` | ```-f``` | Indicates to delete the output path before files are generated (for a clean output) [Default: false] 54 | ```-protocol-path ``` | -p | When specified, indicates the path to a JSON file that contains the chrome debugging protocol definition to use. If not found, one will be generated using the current installed version of chrome. [Default: chrome-debugging-protocol.json] 55 | ```-settings ``` | -s | When specified indicates the path to a JSON file that contains code generation settings. [Default: ./Templates/settings.json] 56 | 57 | ## Customizing output: 58 | 59 | The output that is generated is highly configurable through the use of mustache/handlebars templates. 60 | 61 | The base set of templates is included in /chrome-dev-tools/src/ChromeDevToolsGeneratorCLI/Templates and can be customized to your liking by editing the .hbs files (Handlebars-based templates) and modifying settings.json. 62 | 63 | ## Development 64 | 65 | Unfortunately, MS has not decided to go forward with .csproj support in VS2015, so VS2017 or higher is required. 66 | 67 | Feel free to submit pull requests 68 | 69 | #### General Application Flow 70 | 71 | A general rundown of the flow of this generator is the following: 72 | 73 | 0. Parse command line options and specified settings file. 74 | 1. If a protocol definition file is not found, launches a local instance of Chrome 75 | 1. Gets the commit/version of the launched instance of Chrome 76 | 2. Gets the corresponding Chrome Debugger Protocol definition from Google/Chromium sources 77 | 2. Validates the protocol definition against the schema of the generator classes (to ensure the protocol definition schema hasn't evolved - which would require improving this project) 78 | 3. In memory, using the handlebars-based templates, generates .cs files 79 | 1. Perform a pre-scan of all Types, Commands and Events. 80 | 2. For each include file specified in settings.json, generate the corresponding file. 81 | 3. For each Domain, generate files for each types, events and commands using the corresponding template defined in settings. 82 | 4. Output to disk each generated file. 83 | 84 | #### Thanks 85 | 86 | Thanks to [Handlebars.Net](https://github.com/rexm/Handlebars.Net), [WebSocket4Net](https://github.com/kerryjiang/WebSocket4Net), [NJsonSchema](https://github.com/NJsonSchema/NJsonSchema), [Humanizer](https://github.com/Humanizr/Humanizer) 87 | -------------------------------------------------------------------------------- /chrome-dev-tools-generator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaristaLabs.ChromeDevTools.RemoteInterface", "src\BaristaLabs.ChromeDevTools.RemoteInterface\BaristaLabs.ChromeDevTools.RemoteInterface.csproj", "{272D5654-FF72-41D5-9BFE-E1EBD1C1062C}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6A72E13C-7BE0-4160-A76F-7DD4F713BCD0}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8D7CFCE2-3CD7-47D1-8A3C-D08876DD25FC}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE = LICENSE 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaristaLabs.ChromeDevTools.Core", "src\BaristaLabs.ChromeDevTools.Core\BaristaLabs.ChromeDevTools.Core.csproj", "{59915DF7-8896-4E71-8ECA-AC8E555350D6}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChromeDevToolsGeneratorCLI", "src\ChromeDevToolsGeneratorCLI\ChromeDevToolsGeneratorCLI.csproj", "{73AC2B3E-6CCA-45FC-B45A-7F4A31AE6EF2}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {272D5654-FF72-41D5-9BFE-E1EBD1C1062C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {272D5654-FF72-41D5-9BFE-E1EBD1C1062C}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {272D5654-FF72-41D5-9BFE-E1EBD1C1062C}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {272D5654-FF72-41D5-9BFE-E1EBD1C1062C}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {59915DF7-8896-4E71-8ECA-AC8E555350D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {59915DF7-8896-4E71-8ECA-AC8E555350D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {59915DF7-8896-4E71-8ECA-AC8E555350D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {59915DF7-8896-4E71-8ECA-AC8E555350D6}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {73AC2B3E-6CCA-45FC-B45A-7F4A31AE6EF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {73AC2B3E-6CCA-45FC-B45A-7F4A31AE6EF2}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {73AC2B3E-6CCA-45FC-B45A-7F4A31AE6EF2}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {73AC2B3E-6CCA-45FC-B45A-7F4A31AE6EF2}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {272D5654-FF72-41D5-9BFE-E1EBD1C1062C} = {6A72E13C-7BE0-4160-A76F-7DD4F713BCD0} 44 | {59915DF7-8896-4E71-8ECA-AC8E555350D6} = {6A72E13C-7BE0-4160-A76F-7DD4F713BCD0} 45 | {73AC2B3E-6CCA-45FC-B45A-7F4A31AE6EF2} = {6A72E13C-7BE0-4160-A76F-7DD4F713BCD0} 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {237942A1-9BA3-456A-AE27-A480741014E2} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.Core/BaristaLabs.ChromeDevTools.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | BaristaLabs.ChromeDevTools 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.Core/Chrome.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools 2 | { 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Net.Http; 9 | using System.Runtime.InteropServices; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | /// 14 | /// Represents and manages a chrome process instance 15 | /// 16 | public sealed class Chrome : IDisposable 17 | { 18 | private readonly int m_remoteDebuggingPort; 19 | private DirectoryInfo m_userDirectory; 20 | 21 | internal Chrome(Process chromeProcess, DirectoryInfo userDirectory, int remoteDebuggingPort) 22 | { 23 | Process = chromeProcess ?? throw new ArgumentNullException(nameof(chromeProcess)); 24 | m_userDirectory = userDirectory ?? throw new ArgumentNullException(nameof(userDirectory)); 25 | m_remoteDebuggingPort = remoteDebuggingPort; 26 | } 27 | 28 | /// 29 | /// Gets the Process object for the Chrome Instance 30 | /// 31 | public Process Process { get; private set; } 32 | 33 | public async Task GetChromeVersion() 34 | { 35 | ChromeVersion version; 36 | using (var chromeDebuggerClient = GetDebuggerClient()) 37 | { 38 | var chromeVersionResponseBody = await chromeDebuggerClient.GetStringAsync("/json/version"); 39 | version = JsonConvert.DeserializeObject(chromeVersionResponseBody); 40 | } 41 | 42 | return version; 43 | } 44 | 45 | private HttpClient GetDebuggerClient() 46 | { 47 | var chromeHttpClient = new HttpClient() 48 | { 49 | BaseAddress = new Uri($"http://localhost:{m_remoteDebuggingPort}") 50 | }; 51 | 52 | return chromeHttpClient; 53 | } 54 | 55 | #region IDisposable Support 56 | void Dispose(bool disposing) 57 | { 58 | if (disposing) 59 | { 60 | //Kill the chrome process. 61 | if (Process != null) 62 | { 63 | if (Process.HasExited == false) 64 | Process.WaitForExit(5000); 65 | 66 | if (Process.HasExited == false) 67 | Process.Kill(); 68 | 69 | Process.Dispose(); 70 | Process = null; 71 | } 72 | 73 | //Clean up the target user directory. 74 | if (m_userDirectory != null) 75 | { 76 | //for (int i = 0; i < 10; i++) 77 | //{ 78 | // if (m_userDirectory.Exists == false) 79 | // continue; 80 | 81 | // try 82 | // { 83 | // Thread.Sleep(500); 84 | // m_userDirectory.Delete(true); 85 | // } 86 | // catch 87 | // { 88 | // //Do Nothing. 89 | // } 90 | //} 91 | 92 | //if (m_userDirectory.Exists) 93 | // throw new InvalidOperationException($"Unable to delete the user directory at {m_userDirectory.FullName} after 10 tries."); 94 | 95 | m_userDirectory = null; 96 | } 97 | } 98 | } 99 | 100 | public void Dispose() 101 | { 102 | Dispose(true); 103 | } 104 | #endregion 105 | 106 | /// 107 | /// Creates a new chrome instance 108 | /// 109 | /// 110 | /// 111 | public static Chrome OpenChrome(int remoteDebuggingPort = 9222) 112 | { 113 | string path = Path.GetRandomFileName(); 114 | var directoryInfo = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), path)); 115 | var remoteDebuggingArg = $"--remote-debugging-port={remoteDebuggingPort}"; 116 | var userDirectoryArg = $"--user-data-dir=\"{directoryInfo.FullName}\""; 117 | var chromeProcessArgs = $"{remoteDebuggingArg} {userDirectoryArg} --bwsi --no-first-run"; 118 | 119 | Process chromeProcess; 120 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 121 | { 122 | String programFiles = RuntimeInformation.OSArchitecture == Architecture.X86 123 | ? "Program Files (x86)" 124 | : "Program Files"; 125 | chromeProcess = Process.Start(new ProcessStartInfo($"C:\\{programFiles}\\Google\\Chrome\\Application\\chrome.exe", chromeProcessArgs) { CreateNoWindow = true }); 126 | } 127 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 128 | { 129 | chromeProcess = Process.Start("google-chrome", chromeProcessArgs); 130 | } 131 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 132 | { 133 | chromeProcess = Process.Start("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", chromeProcessArgs); 134 | } 135 | else 136 | { 137 | throw new InvalidOperationException("Unknown or unsupported platform."); 138 | } 139 | 140 | return new Chrome(chromeProcess, directoryInfo, remoteDebuggingPort); 141 | } 142 | 143 | /// 144 | /// Retrieves the browser protocol pdl for the specified chrome version. 145 | /// 146 | /// 147 | /// Um, yeah. See https://github.com/cyrus-and/chrome-remote-interface/issues/10#issuecomment-146032907 148 | /// 149 | /// 150 | public static async Task GetBrowserProtocolForChromeVersion(ChromeVersion chromeVersion) 151 | { 152 | var browserProtocolUrl = $"https://chromium.googlesource.com/chromium/src/+/{chromeVersion.WebKitVersionHash}/third_party/blink/public/devtools_protocol/browser_protocol.pdl?format=TEXT"; 153 | 154 | using (var browserProtocolClient = new HttpClient()) 155 | { 156 | var browserProtocol64 = await browserProtocolClient.GetStringAsync(browserProtocolUrl); 157 | return Encoding.UTF8.GetString(Convert.FromBase64String(browserProtocol64)); 158 | } 159 | } 160 | 161 | /// 162 | /// Retrieves the javascript protocol pdl for the specified chrome version. 163 | /// 164 | public static async Task GetJavaScriptProtocolForChromeVersion(ChromeVersion chromeVersion) 165 | { 166 | var jsProtocolUrl = $"https://chromium.googlesource.com/v8/v8/+/{chromeVersion.V8VersionNumber}/include/js_protocol.pdl?format=TEXT"; 167 | using (var jsProtocolClient = new HttpClient()) 168 | { 169 | var jsProtocol64 = await jsProtocolClient.GetStringAsync(jsProtocolUrl); 170 | return Encoding.UTF8.GetString(Convert.FromBase64String(jsProtocol64)); 171 | } 172 | } 173 | 174 | /// 175 | /// Retrieves the python script that converts a pdl into json for the specified chrome version. 176 | /// 177 | /// 178 | /// 179 | public static async Task GetInspectorProtocolConverterPythonScript(ChromeVersion chromeVersion) 180 | { 181 | var protocolScriptUrl = $"https://chromium.googlesource.com/chromium/src/+/{chromeVersion.WebKitVersionHash}/third_party/inspector_protocol/pdl.py?format=TEXT"; 182 | using (var jsProtocolClient = new HttpClient()) 183 | { 184 | var script64 = await jsProtocolClient.GetStringAsync(protocolScriptUrl); 185 | return Encoding.UTF8.GetString(Convert.FromBase64String(script64)); 186 | } 187 | } 188 | 189 | /// 190 | /// Merges a browserProtocol and jsProtocol into a single protocol definition. 191 | /// 192 | /// 193 | /// 194 | /// 195 | public static JObject MergeJavaScriptProtocolDefinitions(JObject browserProtocol, JObject jsProtocol) 196 | { 197 | //Merge the 2 protocols together. 198 | if (jsProtocol["version"]["majorVersion"] != browserProtocol["version"]["majorVersion"] || 199 | jsProtocol["version"]["minorVersion"] != browserProtocol["version"]["minorVersion"]) 200 | { 201 | throw new InvalidOperationException("Protocol mismatch -- The WebKit and V8 protocol versions should match."); 202 | } 203 | 204 | var result = browserProtocol.DeepClone() as JObject; 205 | foreach (var domain in jsProtocol["domains"]) 206 | { 207 | JArray jDomains = (JArray)result["domains"]; 208 | jDomains.Add(domain); 209 | } 210 | 211 | return result; 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.Core/ChromeVersion.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools 2 | { 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Text.RegularExpressions; 6 | 7 | /// 8 | /// Represents the chrome version information retrieved from chrome. 9 | /// 10 | public class ChromeVersion 11 | { 12 | [JsonProperty(PropertyName = "Browser")] 13 | public string Browser 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | [JsonIgnore] 20 | public string BrowserVersion 21 | { 22 | get { return Browser.Replace("Chrome/", ""); } 23 | } 24 | 25 | [JsonProperty(PropertyName = "Protocol-Version")] 26 | public string ProtocolVersion 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | [JsonProperty(PropertyName = "User-Agent")] 33 | public string UserAgent 34 | { 35 | get; 36 | set; 37 | } 38 | 39 | [JsonProperty(PropertyName = "V8-Version")] 40 | public string V8Version 41 | { 42 | get; 43 | set; 44 | } 45 | 46 | [JsonIgnore] 47 | public string V8VersionNumber 48 | { 49 | get 50 | { 51 | //Get the v8 version 52 | var v8VersionRegex = new Regex(@"^(\d+)\.(\d+)\.(\d+)(\.\d+.*)?"); 53 | var v8VersionMatch = v8VersionRegex.Match(V8Version); 54 | if (v8VersionMatch.Success == false || v8VersionMatch.Groups.Count < 4) 55 | throw new InvalidOperationException($"Unable to determine v8 version number from v8 version string ({V8Version})"); 56 | 57 | return $"{v8VersionMatch.Groups[1].Value}.{v8VersionMatch.Groups[2].Value}.{v8VersionMatch.Groups[3].Value}"; 58 | } 59 | } 60 | 61 | [JsonProperty(PropertyName = "WebKit-Version")] 62 | public string WebKitVersion 63 | { 64 | get; 65 | set; 66 | } 67 | 68 | [JsonIgnore] 69 | public string WebKitVersionHash 70 | { 71 | get 72 | { 73 | //Get the webkit version hash. 74 | var webkitVersionRegex = new Regex(@"\s\(@(\b[0-9a-f]{5,40}\b)"); 75 | var webkitVersionMatch = webkitVersionRegex.Match(WebKitVersion); 76 | if (webkitVersionMatch.Success == false || webkitVersionMatch.Groups.Count != 2) 77 | throw new InvalidOperationException($"Unable to determine webkit version hash from webkit version string ({WebKitVersion})"); 78 | 79 | return webkitVersionMatch.Groups[1].Value; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/BaristaLabs.ChromeDevTools.RemoteInterface.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/CodeGenerationDefinitionTemplateSettings.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using Newtonsoft.Json; 4 | 5 | /// 6 | /// Represents settings around Definition templates. 7 | /// 8 | public class CodeGenerationDefinitionTemplateSettings 9 | { 10 | public CodeGenerationDefinitionTemplateSettings() 11 | { 12 | //Set Defaults; 13 | DomainTemplate = new CodeGenerationTemplateSettings 14 | { 15 | TemplatePath = "domain.hbs", 16 | OutputPath = "{{domainName}}\\{{className}}Adapter.cs", 17 | }; 18 | 19 | CommandTemplate = new CodeGenerationTemplateSettings { 20 | TemplatePath = "command.hbs", 21 | OutputPath = "{{domainName}}\\{{className}}Command.cs", 22 | }; 23 | 24 | EventTemplate = new CodeGenerationTemplateSettings 25 | { 26 | TemplatePath = "event.hbs", 27 | OutputPath = "{{domainName}}\\{{className}}Event.cs", 28 | }; 29 | 30 | TypeObjectTemplate = new CodeGenerationTemplateSettings 31 | { 32 | TemplatePath = "type-object.hbs", 33 | OutputPath = "{{domainName}}\\{{className}}.cs", 34 | }; 35 | 36 | TypeHashTemplate = new CodeGenerationTemplateSettings 37 | { 38 | TemplatePath = "type-hash.hbs", 39 | OutputPath = "{{domainName}}\\{{className}}.cs", 40 | }; 41 | 42 | TypeEnumTemplate = new CodeGenerationTemplateSettings 43 | { 44 | TemplatePath = "type-enum.hbs", 45 | OutputPath = "{{domainName}}\\{{className}}.cs", 46 | }; 47 | } 48 | 49 | [JsonProperty("domainTemplate")] 50 | public CodeGenerationTemplateSettings DomainTemplate 51 | { 52 | get; 53 | set; 54 | } 55 | 56 | [JsonProperty("commandTemplate")] 57 | public CodeGenerationTemplateSettings CommandTemplate 58 | { 59 | get; 60 | set; 61 | } 62 | 63 | [JsonProperty("eventTemplate")] 64 | public CodeGenerationTemplateSettings EventTemplate 65 | { 66 | get; 67 | set; 68 | } 69 | 70 | [JsonProperty("typeObjectTemplate")] 71 | public CodeGenerationTemplateSettings TypeObjectTemplate 72 | { 73 | get; 74 | set; 75 | } 76 | 77 | [JsonProperty("typeHashTemplate")] 78 | public CodeGenerationTemplateSettings TypeHashTemplate 79 | { 80 | get; 81 | set; 82 | } 83 | 84 | [JsonProperty("typeEnumTemplate")] 85 | public CodeGenerationTemplateSettings TypeEnumTemplate 86 | { 87 | get; 88 | set; 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/CodeGenerationSettings.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Settings to be passed to a ICodeGenerator 8 | /// 9 | public sealed class CodeGenerationSettings 10 | { 11 | public CodeGenerationSettings() 12 | { 13 | //Set defaults 14 | Include = new List(); 15 | IncludeDeprecatedDomains = true; 16 | RootNamespace = "BaristaLabs.ChromeDevTools"; 17 | DefinitionTemplates = new CodeGenerationDefinitionTemplateSettings(); 18 | TemplatesPath = "Templates"; 19 | UsingStatements = new List() 20 | { 21 | "System" 22 | }; 23 | } 24 | 25 | /// 26 | /// Collection of templates that will be parsed and output in the target folder. 27 | /// 28 | [JsonProperty("include")] 29 | public ICollection Include 30 | { 31 | get; 32 | set; 33 | } 34 | 35 | /// 36 | /// Indicates whether or not domains marked as depreciated will be generated. (Default: true) 37 | /// 38 | [JsonProperty("includeDeprecatedDomains")] 39 | public bool IncludeDeprecatedDomains 40 | { 41 | get; 42 | set; 43 | } 44 | 45 | /// 46 | /// Gets or sets the root namespace of generated classes. 47 | /// 48 | [JsonProperty("rootNamespace")] 49 | public string RootNamespace 50 | { 51 | get; 52 | set; 53 | } 54 | 55 | /// 56 | /// Gets the version number of the runtime. 57 | /// 58 | [JsonProperty("runtimeVersion")] 59 | public string RuntimeVersion 60 | { 61 | get; 62 | set; 63 | } 64 | 65 | [JsonProperty("definitionTemplates")] 66 | public CodeGenerationDefinitionTemplateSettings DefinitionTemplates 67 | { 68 | get; 69 | set; 70 | } 71 | 72 | [JsonProperty("templatesPath")] 73 | public string TemplatesPath 74 | { 75 | get; 76 | set; 77 | } 78 | 79 | /// 80 | /// The using statements that will be included on each generated file. 81 | /// 82 | [JsonProperty("usingStatements")] 83 | public ICollection UsingStatements 84 | { 85 | get; 86 | set; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/CodeGenerationTemplateSettings.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using Newtonsoft.Json; 4 | 5 | /// 6 | /// Defines settings around templates 7 | /// 8 | public class CodeGenerationTemplateSettings 9 | { 10 | [JsonProperty("templatePath")] 11 | public string TemplatePath 12 | { 13 | get; 14 | set; 15 | } 16 | 17 | [JsonProperty("outputPath")] 18 | public string OutputPath 19 | { 20 | get; 21 | set; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/CodeGeneratorBase.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | /// 9 | /// Represents a base implementation of a code generator. 10 | /// 11 | /// 12 | public abstract class CodeGeneratorBase : ICodeGenerator 13 | where T : IDefinition 14 | { 15 | private readonly IServiceProvider m_serviceProvider; 16 | private readonly Lazy m_settings; 17 | private readonly Lazy m_templatesManager; 18 | 19 | /// 20 | /// Gets the service provider associated with the generator. 21 | /// 22 | public IServiceProvider ServiceProvider 23 | { 24 | get { return m_serviceProvider; } 25 | } 26 | 27 | /// 28 | /// Gets the code generation settings associated with the generator. 29 | /// 30 | public CodeGenerationSettings Settings 31 | { 32 | get { return m_settings.Value; } 33 | } 34 | 35 | /// 36 | /// Gets a template manager associated with the generator. 37 | /// 38 | public TemplatesManager TemplatesManager 39 | { 40 | get { return m_templatesManager.Value; } 41 | } 42 | 43 | protected CodeGeneratorBase(IServiceProvider serviceProvider) 44 | { 45 | m_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); 46 | m_settings = new Lazy(() => m_serviceProvider.GetRequiredService()); 47 | m_templatesManager = new Lazy(() => m_serviceProvider.GetRequiredService()); 48 | } 49 | 50 | public abstract IDictionary GenerateCode(T item, CodeGeneratorContext context); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/CodeGeneratorContext.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Represents the current context of the code generator. 8 | /// 9 | public sealed class CodeGeneratorContext 10 | { 11 | public DomainDefinition Domain 12 | { 13 | get; 14 | set; 15 | } 16 | 17 | public Dictionary KnownTypes 18 | { 19 | get; 20 | set; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/CommandGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using Humanizer; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | /// 9 | /// Generates code for Command Definitions 10 | /// 11 | public sealed class CommandGenerator : CodeGeneratorBase 12 | { 13 | public CommandGenerator(IServiceProvider serviceProvider) 14 | : base(serviceProvider) 15 | { 16 | } 17 | 18 | public override IDictionary GenerateCode(CommandDefinition commandDefinition, CodeGeneratorContext context) 19 | { 20 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase); 21 | 22 | if (String.IsNullOrWhiteSpace(Settings.DefinitionTemplates.CommandTemplate.TemplatePath)) 23 | return result; 24 | 25 | var commandGenerator = TemplatesManager.GetGeneratorForTemplate(Settings.DefinitionTemplates.CommandTemplate); 26 | 27 | var className = commandDefinition.Name.Dehumanize(); 28 | string codeResult = commandGenerator(new 29 | { 30 | command = commandDefinition, 31 | className = className, 32 | domain = context.Domain, 33 | rootNamespace = Settings.RootNamespace, 34 | context = context 35 | }); 36 | 37 | var outputPath = Utility.ReplaceTokensInPath(Settings.DefinitionTemplates.CommandTemplate.OutputPath, className, context, Settings); 38 | result.Add(outputPath, codeResult); 39 | return result; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/CommandInfo.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | /// 4 | /// Represents information about a Chrome Debugger Protocol command. 5 | /// 6 | public sealed class CommandInfo 7 | { 8 | public string CommandName 9 | { 10 | get; 11 | set; 12 | } 13 | 14 | public string FullTypeName 15 | { 16 | get; 17 | set; 18 | } 19 | public string FullResponseTypeName 20 | { 21 | get; 22 | set; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/DomainGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using Humanizer; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | /// 11 | /// Generates code for Domain Definitions 12 | /// 13 | public sealed class DomainGenerator : CodeGeneratorBase 14 | { 15 | public DomainGenerator(IServiceProvider serviceProvider) 16 | : base(serviceProvider) 17 | { 18 | } 19 | 20 | public override IDictionary GenerateCode(DomainDefinition domainDefinition, CodeGeneratorContext context) 21 | { 22 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase); 23 | 24 | var typeGenerator = ServiceProvider.GetRequiredService>(); 25 | foreach (var type in domainDefinition.Types) 26 | { 27 | typeGenerator.GenerateCode(type, context) 28 | .ToList() 29 | .ForEach(x => result.Add(x.Key, x.Value)); 30 | } 31 | 32 | var eventGenerator = ServiceProvider.GetRequiredService>(); 33 | foreach (var @event in domainDefinition.Events) 34 | { 35 | eventGenerator.GenerateCode(@event, context) 36 | .ToList() 37 | .ForEach(x => result.Add(x.Key, x.Value)); 38 | } 39 | 40 | var commandGenerator = ServiceProvider.GetRequiredService>(); 41 | foreach (var command in domainDefinition.Commands) 42 | { 43 | commandGenerator.GenerateCode(command, context) 44 | .ToList() 45 | .ForEach(x => result.Add(x.Key, x.Value)); 46 | } 47 | 48 | if (String.IsNullOrWhiteSpace(Settings.DefinitionTemplates.DomainTemplate.TemplatePath)) 49 | return result; 50 | 51 | var domainGenerator = TemplatesManager.GetGeneratorForTemplate(Settings.DefinitionTemplates.DomainTemplate); 52 | 53 | var className = domainDefinition.Name.Dehumanize(); 54 | 55 | string codeResult = domainGenerator(new 56 | { 57 | domain = domainDefinition, 58 | className = className, 59 | rootNamespace = Settings.RootNamespace, 60 | context = context 61 | }); 62 | 63 | var outputPath = Utility.ReplaceTokensInPath(Settings.DefinitionTemplates.DomainTemplate.OutputPath, className, context, Settings); 64 | result.Add(outputPath, codeResult); 65 | 66 | return result; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/EventGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using Humanizer; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | /// 9 | /// Generates code for Event Definitions 10 | /// 11 | public sealed class EventGenerator : CodeGeneratorBase 12 | { 13 | public EventGenerator(IServiceProvider serviceProvider) 14 | : base(serviceProvider) 15 | { 16 | } 17 | 18 | public override IDictionary GenerateCode(EventDefinition eventDefinition, CodeGeneratorContext context) 19 | { 20 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase); 21 | 22 | if (String.IsNullOrWhiteSpace(Settings.DefinitionTemplates.EventTemplate.TemplatePath)) 23 | return result; 24 | 25 | var eventGenerator = TemplatesManager.GetGeneratorForTemplate(Settings.DefinitionTemplates.EventTemplate); 26 | 27 | var className = eventDefinition.Name.Dehumanize(); 28 | 29 | string codeResult = eventGenerator(new 30 | { 31 | @event = eventDefinition, 32 | className = className, 33 | domain = context.Domain, 34 | rootNamespace = Settings.RootNamespace, 35 | context = context 36 | }); 37 | 38 | var outputPath = Utility.ReplaceTokensInPath(Settings.DefinitionTemplates.EventTemplate.OutputPath, className, context, Settings); 39 | result.Add(outputPath, codeResult); 40 | 41 | return result; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/EventInfo.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | /// 4 | /// Represents information about a Chrome Debugger Protocol event. 5 | /// 6 | public sealed class EventInfo 7 | { 8 | public string EventName 9 | { 10 | get; 11 | set; 12 | } 13 | 14 | public string FullTypeName 15 | { 16 | get; 17 | set; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/ICodeGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Represents a code generator that generates code files for a specific IDefinition type. 8 | /// 9 | /// 10 | public interface ICodeGenerator 11 | where T : IDefinition 12 | { 13 | /// 14 | /// Generates one or more code files for the specified IDefinition item 15 | /// 16 | /// 17 | /// 18 | /// 19 | IDictionary GenerateCode(T item, CodeGeneratorContext context); 20 | } 21 | } -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/IServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | 7 | /// 8 | /// Contains extensions for IServiceProvider. 9 | /// 10 | public static class IServiceProviderExtensions 11 | { 12 | /// 13 | /// Adds a pre-defined set of code generator services to provide Chrome Remote Interface generation. 14 | /// 15 | /// 16 | /// 17 | /// 18 | public static IServiceCollection AddCodeGenerationServices(this IServiceCollection serviceCollection, CodeGenerationSettings settings) 19 | { 20 | if (settings == null) 21 | throw new ArgumentNullException(nameof(settings)); 22 | 23 | return serviceCollection 24 | .AddSingleton(settings) 25 | .AddSingleton() 26 | .AddSingleton>((sp) => new ProtocolGenerator(sp)) 27 | .AddSingleton>((sp) => new DomainGenerator(sp)) 28 | .AddSingleton>((sp) => new TypeGenerator(sp)) 29 | .AddSingleton>((sp) => new CommandGenerator(sp)) 30 | .AddSingleton>((sp) => new EventGenerator(sp)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/ProtocolGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using Humanizer; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | 11 | /// 12 | /// Represents an object that generates a protocol definition. 13 | /// 14 | public sealed class ProtocolGenerator : CodeGeneratorBase 15 | { 16 | public ProtocolGenerator(IServiceProvider serviceProvider) 17 | : base(serviceProvider) 18 | { 19 | } 20 | 21 | public override IDictionary GenerateCode(ProtocolDefinition protocolDefinition, CodeGeneratorContext context) 22 | { 23 | if (String.IsNullOrWhiteSpace(Settings.TemplatesPath)) 24 | { 25 | Settings.TemplatesPath = Path.GetDirectoryName(Settings.TemplatesPath); 26 | } 27 | 28 | ICollection domains; 29 | if (Settings.IncludeDeprecatedDomains) 30 | domains = protocolDefinition.Domains; 31 | else 32 | domains = protocolDefinition.Domains 33 | .Where(d => d.Deprecated == false) 34 | .ToList(); 35 | 36 | //Get commandinfos as an array. 37 | ICollection commands = new List(); 38 | 39 | foreach (var domain in domains) 40 | { 41 | foreach (var command in domain.Commands) 42 | { 43 | commands.Add(new CommandInfo 44 | { 45 | CommandName = $"{domain.Name}.{command.Name}", 46 | FullTypeName = $"{domain.Name.Dehumanize()}.{command.Name.Dehumanize()}Command", 47 | FullResponseTypeName = $"{domain.Name.Dehumanize()}.{command.Name.Dehumanize()}CommandResponse" 48 | }); 49 | } 50 | } 51 | 52 | //Get eventinfos as an array 53 | ICollection events = new List(); 54 | 55 | foreach(var domain in domains) 56 | { 57 | foreach(var @event in domain.Events) 58 | { 59 | events.Add(new EventInfo 60 | { 61 | EventName = $"{domain.Name}.{@event.Name}", 62 | FullTypeName = $"{domain.Name.Dehumanize()}.{@event.Name.Dehumanize()}Event" 63 | }); 64 | } 65 | } 66 | 67 | //Get typeinfos as a dictionary. 68 | var types = GetTypesInDomain(domains); 69 | 70 | //Create an object that contains information that include templates can use. 71 | var includeData = new { 72 | chromeVersion = protocolDefinition.ChromeVersion, 73 | runtimeVersion = Settings.RuntimeVersion, 74 | rootNamespace = Settings.RootNamespace, 75 | domains = domains, 76 | commands = commands, 77 | events = events, 78 | types = types.Select(kvp => kvp.Value).ToList() 79 | }; 80 | 81 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase); 82 | 83 | //Generate include files from templates. 84 | foreach (var include in Settings.Include) 85 | { 86 | var includeCodeGenerator = TemplatesManager.GetGeneratorForTemplate(include); 87 | var includeCodeResult = includeCodeGenerator(includeData); 88 | result.Add(include.OutputPath, includeCodeResult); 89 | } 90 | 91 | //Generate code for each domain, type, command, event from their respective templates. 92 | GenerateCode(domains, types) 93 | .ToList() 94 | .ForEach(x => result.Add(x.Key, x.Value)); 95 | 96 | return result; 97 | } 98 | 99 | private Dictionary GetTypesInDomain(ICollection domains) 100 | { 101 | var knownTypes = new Dictionary(StringComparer.OrdinalIgnoreCase); 102 | 103 | //First pass - get all top-level types. 104 | foreach (var domain in domains) 105 | { 106 | foreach (var type in domain.Types) 107 | { 108 | TypeInfo typeInfo; 109 | switch (type.Type) 110 | { 111 | case "object": 112 | typeInfo = new TypeInfo 113 | { 114 | IsPrimitive = false, 115 | TypeName = type.Id.Dehumanize(), 116 | }; 117 | break; 118 | case "string": 119 | if (type.Enum != null && type.Enum.Count() > 0) 120 | typeInfo = new TypeInfo 121 | { 122 | ByRef = true, 123 | IsPrimitive = false, 124 | TypeName = type.Id.Dehumanize(), 125 | }; 126 | else 127 | typeInfo = new TypeInfo 128 | { 129 | IsPrimitive = true, 130 | TypeName = "string" 131 | }; 132 | break; 133 | case "array": 134 | if ((type.Items == null || String.IsNullOrWhiteSpace(type.Items.Type)) && type.Items.TypeReference != "StringIndex") 135 | { 136 | throw new NotImplementedException("Did not expect a top-level domain array type to specify a TypeReference"); 137 | } 138 | 139 | string itemType; 140 | switch (type.Items.Type) 141 | { 142 | case "string": 143 | itemType = "string"; 144 | break; 145 | case "number": 146 | itemType = "double"; 147 | break; 148 | case null: 149 | if (String.IsNullOrWhiteSpace(type.Items.TypeReference)) 150 | throw new NotImplementedException($"Did not expect a top-level domain array type to have a null type and a null or whitespace type reference."); 151 | 152 | switch (type.Items.TypeReference) 153 | { 154 | case "StringIndex": 155 | itemType = "string"; 156 | break; 157 | default: 158 | throw new NotImplementedException($"Did not expect a top-level domain array type to specify a type reference of {type.Items.TypeReference}"); 159 | } 160 | break; 161 | default: 162 | throw new NotImplementedException($"Did not expect a top-level domain array type to specify items of type {type.Items.Type}"); 163 | } 164 | typeInfo = new TypeInfo 165 | { 166 | IsPrimitive = true, 167 | TypeName = $"{itemType}[]" 168 | }; 169 | break; 170 | case "number": 171 | typeInfo = new TypeInfo 172 | { 173 | ByRef = true, 174 | IsPrimitive = true, 175 | TypeName = "double" 176 | }; 177 | break; 178 | case "integer": 179 | typeInfo = new TypeInfo 180 | { 181 | ByRef = true, 182 | IsPrimitive = true, 183 | TypeName = "long" 184 | }; 185 | break; 186 | default: 187 | throw new InvalidOperationException($"Unknown Type Definition Type: {type.Id}"); 188 | } 189 | 190 | typeInfo.Namespace = domain.Name.Dehumanize(); 191 | typeInfo.SourcePath = $"{domain.Name}.{type.Id}"; 192 | knownTypes.Add($"{domain.Name}.{type.Id}", typeInfo); 193 | } 194 | } 195 | 196 | return knownTypes; 197 | } 198 | 199 | private IDictionary GenerateCode(ICollection domains, Dictionary knownTypes) 200 | { 201 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase); 202 | 203 | var domainGenerator = ServiceProvider.GetRequiredService>(); 204 | 205 | //Generate types/events/commands for all domains. 206 | foreach (var domain in domains) 207 | { 208 | domainGenerator.GenerateCode(domain, new CodeGeneratorContext { Domain = domain, KnownTypes = knownTypes }) 209 | .ToList() 210 | .ForEach(x => result.Add(x.Key, x.Value)); 211 | } 212 | 213 | return result; 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/TemplatesManager.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using HandlebarsDotNet; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using Humanizer; 8 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 9 | using System.Linq; 10 | using System.Text; 11 | 12 | /// 13 | /// Represents a class that manages templates and their associated generators. 14 | /// 15 | public sealed class TemplatesManager 16 | { 17 | private readonly IDictionary> m_templateGenerators = new Dictionary>(StringComparer.OrdinalIgnoreCase); 18 | private readonly CodeGenerationSettings m_settings; 19 | 20 | /// 21 | /// Gets the code generation settings associated with the protocol generator 22 | /// 23 | public CodeGenerationSettings Settings 24 | { 25 | get { return m_settings; } 26 | } 27 | 28 | public TemplatesManager(CodeGenerationSettings settings) 29 | { 30 | m_settings = settings ?? throw new ArgumentNullException(nameof(settings)); 31 | } 32 | 33 | /// 34 | /// Returns a generator singleton for the specified template path. 35 | /// 36 | /// 37 | /// 38 | public Func GetGeneratorForTemplate(CodeGenerationTemplateSettings templateSettings) 39 | { 40 | var templatePath = templateSettings.TemplatePath; 41 | if (m_templateGenerators.ContainsKey(templatePath)) 42 | return m_templateGenerators[templatePath]; 43 | 44 | var targetTemplate = templatePath; 45 | if (!Path.IsPathRooted(targetTemplate)) 46 | targetTemplate = Path.Combine(Settings.TemplatesPath, targetTemplate); 47 | 48 | if (!File.Exists(targetTemplate)) 49 | throw new FileNotFoundException($"Unable to locate a template at {targetTemplate} - please ensure that a template file exists at this location."); 50 | 51 | var templateContents = File.ReadAllText(targetTemplate); 52 | 53 | Handlebars.RegisterHelper("dehumanize", (writer, context, arguments) => 54 | { 55 | if (arguments.Length != 1) 56 | { 57 | throw new HandlebarsException("{{humanize}} helper must have exactly one argument"); 58 | } 59 | 60 | var str = arguments[0].ToString(); 61 | 62 | //Some overrides for values that start with '-' -- this fixes two instances in Runtime.UnserializableValue 63 | if (str.StartsWith("-")) 64 | { 65 | str = $"Negative{str.Dehumanize()}"; 66 | } 67 | else 68 | { 69 | str = str.Dehumanize(); 70 | } 71 | 72 | writer.WriteSafeString(str.Dehumanize()); 73 | }); 74 | 75 | Handlebars.RegisterHelper("xml-code-comment", (writer, context, arguments) => 76 | { 77 | if (arguments.Length < 1) 78 | { 79 | throw new HandlebarsException("{{code-comment}} helper must have at least one argument"); 80 | } 81 | 82 | var str = arguments[0] == null ? "" : arguments[0].ToString(); 83 | 84 | if (String.IsNullOrWhiteSpace(str)) 85 | { 86 | switch (context) 87 | { 88 | case ProtocolDefinitionItem pdi: 89 | str = $"{pdi.Name}"; 90 | break; 91 | default: 92 | str = context.className; 93 | break; 94 | } 95 | } 96 | 97 | var frontPaddingObj = arguments.ElementAtOrDefault(1); 98 | var frontPadding = 1; 99 | if (frontPaddingObj != null) 100 | { 101 | int.TryParse(frontPaddingObj.ToString(), out frontPadding); 102 | } 103 | 104 | str = Utility.ReplaceLineEndings(str, Environment.NewLine + new StringBuilder(4 * frontPadding).Insert(0, " ", frontPadding) + "/// "); 105 | 106 | writer.WriteSafeString(str); 107 | }); 108 | 109 | Handlebars.RegisterHelper("typemap", (writer, context, arguments) => 110 | { 111 | var typeDefinition = context as TypeDefinition; 112 | if (typeDefinition == null) 113 | { 114 | throw new HandlebarsException("{{typemap}} helper expects to be in the context of a TypeDefinition."); 115 | } 116 | 117 | if (arguments.Length != 1) 118 | { 119 | throw new HandlebarsException("{{typemap}} helper expects exactly one argument - the CodeGeneratorContext."); 120 | } 121 | 122 | var codeGenContext = arguments[0] as CodeGeneratorContext; 123 | if (codeGenContext == null) 124 | throw new InvalidOperationException("Expected context argument to be non-null."); 125 | 126 | var mappedType = Utility.GetTypeMappingForType(typeDefinition, codeGenContext.Domain, codeGenContext.KnownTypes); 127 | writer.WriteSafeString(mappedType); 128 | }); 129 | 130 | Handlebars.Configuration.TextEncoder = null; 131 | return Handlebars.Compile(templateContents); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/TypeGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using Humanizer; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | /// 9 | /// Generates code for Type Definitions 10 | /// 11 | public sealed class TypeGenerator : CodeGeneratorBase 12 | { 13 | public TypeGenerator(IServiceProvider serviceProvider) 14 | : base(serviceProvider) 15 | { 16 | } 17 | 18 | public override IDictionary GenerateCode(TypeDefinition typeDefinition, CodeGeneratorContext context) 19 | { 20 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase); 21 | 22 | if (context.KnownTypes == null) 23 | throw new InvalidOperationException("Expected knowntypes to be specified in context"); 24 | 25 | if (context.Domain == null) 26 | throw new InvalidOperationException("Expected domain to be specified in context"); 27 | 28 | var typeInfo = context.KnownTypes[$"{context.Domain.Name}.{typeDefinition.Id}"]; 29 | if (typeInfo.IsPrimitive) 30 | return result; 31 | 32 | //Base the code generation template on the specified type definition type. 33 | CodeGenerationTemplateSettings templateSettings; 34 | switch (typeDefinition.Type) 35 | { 36 | case "object": 37 | templateSettings = Settings.DefinitionTemplates.TypeObjectTemplate; 38 | break; 39 | case "string": 40 | templateSettings = Settings.DefinitionTemplates.TypeEnumTemplate; 41 | break; 42 | default: 43 | throw new InvalidOperationException($"Unsupported Type Definition Type: {typeDefinition.Type}"); 44 | } 45 | 46 | // Special override for the headers object to be an open object. 47 | // TODO: make this kind of override configurable. 48 | if (context.Domain.Name == "Network" && typeDefinition.Id == "Headers") 49 | { 50 | templateSettings = Settings.DefinitionTemplates.TypeHashTemplate; 51 | } 52 | 53 | if (String.IsNullOrWhiteSpace(templateSettings.TemplatePath)) 54 | return result; 55 | 56 | var typeGenerator = TemplatesManager.GetGeneratorForTemplate(templateSettings); 57 | 58 | var className = typeDefinition.Id.Dehumanize(); 59 | var codeResult = typeGenerator(new 60 | { 61 | type = typeDefinition, 62 | className = className, 63 | domain = context.Domain, 64 | rootNamespace = Settings.RootNamespace, 65 | context = context 66 | }); 67 | 68 | var outputPath = Utility.ReplaceTokensInPath(templateSettings.OutputPath, className, context, Settings); 69 | result.Add(outputPath, codeResult); 70 | 71 | return result; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/TypeInfo.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | /// 4 | /// Represents information about a Chrome Debugger Protocol type. 5 | /// 6 | public sealed class TypeInfo 7 | { 8 | public bool ByRef 9 | { 10 | get; 11 | set; 12 | } 13 | 14 | public string Namespace 15 | { 16 | get; 17 | set; 18 | } 19 | 20 | public bool IsPrimitive 21 | { 22 | get; 23 | set; 24 | } 25 | 26 | public string TypeName 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | public string SourcePath 33 | { 34 | get; 35 | set; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/CodeGen/Utility.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text.RegularExpressions; 7 | 8 | /// 9 | /// Contains various utility methods. 10 | /// 11 | public static class Utility 12 | { 13 | /// 14 | /// Replaces tokens in the target path. 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public static string ReplaceTokensInPath(string path, string className, CodeGeneratorContext context, CodeGenerationSettings settings) 22 | { 23 | path = path.Replace("{{className}}", className); 24 | path = path.Replace("{{rootNamespace}}", settings.RootNamespace); 25 | path = path.Replace("{{templatePath}}", settings.TemplatesPath); 26 | path = path.Replace("{{domainName}}", context.Domain.Name); 27 | return path; 28 | } 29 | 30 | /// 31 | /// For the given type, gets the associated type mapping given the domain and known types. 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | /// 38 | public static string GetTypeMappingForType(TypeDefinition typeDefinition, DomainDefinition domainDefinition, IDictionary knownTypes, bool isArray = false) 39 | { 40 | var type = typeDefinition.Type; 41 | 42 | if (String.IsNullOrWhiteSpace(type)) 43 | type = typeDefinition.TypeReference; 44 | 45 | string mappedType = null; 46 | if (type.Contains(".") && knownTypes.ContainsKey(type)) 47 | { 48 | var typeInfo = knownTypes[type]; 49 | if (typeInfo.IsPrimitive) 50 | { 51 | var primitiveType = typeInfo.TypeName; 52 | 53 | if (typeDefinition.Optional && typeInfo.ByRef) 54 | primitiveType += "?"; 55 | 56 | if (isArray) 57 | primitiveType += "[]"; 58 | 59 | return primitiveType; 60 | } 61 | mappedType = $"{typeInfo.Namespace}.{typeInfo.TypeName}"; 62 | } 63 | 64 | if (mappedType == null) 65 | { 66 | var fullyQualifiedTypeName = $"{domainDefinition.Name}.{type}"; 67 | 68 | if (knownTypes.ContainsKey(fullyQualifiedTypeName)) 69 | { 70 | var typeInfo = knownTypes[fullyQualifiedTypeName]; 71 | 72 | mappedType = typeInfo.TypeName; 73 | if (typeInfo.ByRef && typeDefinition.Optional) 74 | mappedType += "?"; 75 | } 76 | } 77 | 78 | 79 | if (mappedType == null) 80 | { 81 | switch (type) 82 | { 83 | case "number": 84 | mappedType = typeDefinition.Optional ? "double?" : "double"; 85 | break; 86 | case "integer": 87 | mappedType = typeDefinition.Optional ? "long?" : "long"; 88 | break; 89 | case "boolean": 90 | mappedType = typeDefinition.Optional ? "bool?" : "bool"; 91 | break; 92 | case "string": 93 | mappedType = "string"; 94 | break; 95 | case "object": 96 | case "any": 97 | mappedType = "object"; 98 | break; 99 | case "array": 100 | mappedType = GetTypeMappingForType(typeDefinition.Items, domainDefinition, knownTypes, true); 101 | break; 102 | case "binary": 103 | mappedType = "byte[]"; 104 | break; 105 | default: 106 | throw new InvalidOperationException($"Unmapped data type: {type}"); 107 | } 108 | } 109 | 110 | if (isArray) 111 | mappedType += "[]"; 112 | 113 | return mappedType; 114 | } 115 | 116 | public static string ReplaceLineEndings(string value, string replacement = null) 117 | { 118 | if (String.IsNullOrEmpty(value)) 119 | { 120 | return value; 121 | } 122 | 123 | if (replacement == null) 124 | replacement = string.Empty; 125 | 126 | return Regex.Replace(value, @"\r\n?|\n|\u2028|\u2029", replacement, RegexOptions.Compiled); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/Converters/BooleanJsonConverter.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.Converters 2 | { 3 | using Newtonsoft.Json; 4 | using System; 5 | 6 | /// 7 | /// Handles converting JSON string values into a C# boolean data type. 8 | /// 9 | public class BooleanJsonConverter : JsonConverter 10 | { 11 | /// 12 | /// Determines whether this instance can convert the specified object type. 13 | /// 14 | /// Type of the object. 15 | /// 16 | /// true if this instance can convert the specified object type; otherwise, false. 17 | /// 18 | public override bool CanConvert(Type objectType) 19 | { 20 | // Handle only boolean types. 21 | return objectType == typeof(bool); 22 | } 23 | 24 | /// 25 | /// Reads the JSON representation of the object. 26 | /// 27 | /// The to read from. 28 | /// Type of the object. 29 | /// The existing value of object being read. 30 | /// The calling serializer. 31 | /// 32 | /// The object value. 33 | /// 34 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 35 | { 36 | switch (reader.Value.ToString().ToLower().Trim()) 37 | { 38 | case "true": 39 | case "yes": 40 | case "y": 41 | case "1": 42 | return true; 43 | case "false": 44 | case "no": 45 | case "n": 46 | case "0": 47 | return false; 48 | } 49 | 50 | // If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message. 51 | return new JsonSerializer().Deserialize(reader, objectType); 52 | } 53 | 54 | /// 55 | /// Specifies that this converter will not participate in writing results. 56 | /// 57 | public override bool CanWrite { get { return false; } } 58 | 59 | /// 60 | /// Writes the JSON representation of the object. 61 | /// 62 | /// The to write to.The value.The calling serializer. 63 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 64 | { 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/CommandDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | 7 | public sealed class CommandDefinition : ProtocolDefinitionItem 8 | { 9 | public CommandDefinition() 10 | { 11 | Handlers = new HashSet(); 12 | 13 | Parameters = new Collection(); 14 | Returns = new Collection(); 15 | } 16 | 17 | [JsonProperty(PropertyName = "handlers")] 18 | public ICollection Handlers 19 | { 20 | get; 21 | set; 22 | } 23 | 24 | [JsonProperty(PropertyName = "parameters")] 25 | public ICollection Parameters 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | [JsonProperty(PropertyName = "returns")] 32 | public ICollection Returns 33 | { 34 | get; 35 | set; 36 | } 37 | 38 | [JsonProperty(PropertyName = "redirect")] 39 | public string Redirect 40 | { 41 | get; 42 | set; 43 | } 44 | 45 | [JsonIgnore] 46 | public bool NoParameters => Parameters == null || Parameters.Count == 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/DomainDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | 7 | public sealed class DomainDefinition : ProtocolDefinitionItem 8 | { 9 | public DomainDefinition() 10 | { 11 | Dependencies = new HashSet(); 12 | 13 | Types = new Collection(); 14 | Events = new Collection(); 15 | Commands = new Collection(); 16 | } 17 | 18 | [JsonProperty(PropertyName = "domain")] 19 | public override string Name 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | [JsonProperty(PropertyName = "types")] 26 | public ICollection Types 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | [JsonProperty(PropertyName = "commands")] 33 | public ICollection Commands 34 | { 35 | get; 36 | set; 37 | } 38 | 39 | [JsonProperty(PropertyName = "events")] 40 | public ICollection Events 41 | { 42 | get; 43 | set; 44 | } 45 | 46 | [JsonProperty(PropertyName = "dependencies")] 47 | public ICollection Dependencies 48 | { 49 | get; 50 | set; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/EventDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | 7 | public sealed class EventDefinition : ProtocolDefinitionItem 8 | { 9 | public EventDefinition() 10 | { 11 | Parameters = new Collection(); 12 | } 13 | 14 | [JsonProperty(PropertyName = "parameters")] 15 | public ICollection Parameters 16 | { 17 | get; 18 | set; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/IDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | /// 4 | /// Interface that identifies definition classes. 5 | /// 6 | public interface IDefinition 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/ProtocolDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | 7 | public sealed class ProtocolDefinition : IDefinition 8 | { 9 | public ProtocolDefinition() 10 | { 11 | Domains = new Collection(); 12 | } 13 | 14 | [JsonProperty(PropertyName = "chromeVersion", Required = Required.Always)] 15 | public ChromeVersion ChromeVersion 16 | { 17 | get; 18 | set; 19 | } 20 | 21 | [JsonProperty(PropertyName = "version", Required = Required.Always)] 22 | public Version Version 23 | { 24 | get; 25 | set; 26 | } 27 | 28 | [JsonProperty(PropertyName = "domains", Required = Required.Always)] 29 | public ICollection Domains 30 | { 31 | get; 32 | set; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/ProtocolDefinitionItem.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.Converters; 4 | using Newtonsoft.Json; 5 | 6 | public abstract class ProtocolDefinitionItem : IDefinition 7 | { 8 | 9 | [JsonProperty(PropertyName = "deprecated")] 10 | public bool Deprecated 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | [JsonProperty(PropertyName = "description")] 17 | public string Description 18 | { 19 | get; 20 | set; 21 | } 22 | 23 | [JsonProperty(PropertyName = "experimental")] 24 | [JsonConverter(typeof(BooleanJsonConverter))] 25 | public bool Experimental 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | [JsonProperty(PropertyName = "name")] 32 | public virtual string Name 33 | { 34 | get; 35 | set; 36 | } 37 | 38 | public override string ToString() 39 | { 40 | return Name; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/TypeDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | using BaristaLabs.ChromeDevTools.RemoteInterface.Converters; 4 | using Newtonsoft.Json; 5 | using NJsonSchema; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Collections.ObjectModel; 9 | 10 | public sealed class TypeDefinition : ProtocolDefinitionItem 11 | { 12 | public TypeDefinition() 13 | { 14 | Enum = new HashSet(); 15 | Properties = new Collection(); 16 | } 17 | 18 | [JsonProperty(PropertyName = "id")] 19 | public string Id 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | [JsonProperty(PropertyName = "type")] 26 | public string Type 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | [JsonProperty(PropertyName = "enum")] 33 | public ICollection Enum 34 | { 35 | get; 36 | set; 37 | } 38 | 39 | [JsonProperty(PropertyName = "properties")] 40 | public ICollection Properties 41 | { 42 | get; 43 | set; 44 | } 45 | 46 | [JsonProperty(PropertyName = "items")] 47 | public TypeDefinition Items 48 | { 49 | get; 50 | set; 51 | } 52 | 53 | [JsonProperty(PropertyName = "minItems")] 54 | public int MinItems 55 | { 56 | get; 57 | set; 58 | } 59 | 60 | [JsonProperty(PropertyName = "maxItems")] 61 | public int MaxItems 62 | { 63 | get; 64 | set; 65 | } 66 | 67 | [JsonProperty(PropertyName = "$ref")] 68 | public string TypeReference 69 | { 70 | get; 71 | set; 72 | } 73 | 74 | [JsonProperty(PropertyName = "optional")] 75 | [JsonConverter(typeof(BooleanJsonConverter))] 76 | public bool Optional 77 | { 78 | get; 79 | set; 80 | } 81 | 82 | public override string ToString() 83 | { 84 | if (!String.IsNullOrWhiteSpace(Id)) 85 | return Id; 86 | 87 | if (!String.IsNullOrWhiteSpace(Name)) 88 | return Name; 89 | 90 | return $"Ref: {TypeReference}"; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/BaristaLabs.ChromeDevTools.RemoteInterface/ProtocolDefinition/Version.cs: -------------------------------------------------------------------------------- 1 | namespace BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition 2 | { 3 | using Newtonsoft.Json; 4 | using System; 5 | 6 | /// 7 | /// Indicates the version of the Protocol Definition. 8 | /// 9 | public sealed class Version : IComparable 10 | { 11 | [JsonProperty(PropertyName = "major")] 12 | public string Major 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | [JsonProperty(PropertyName = "minor")] 19 | public string Minor 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | public int CompareTo(Version other) 26 | { 27 | if (other == null) 28 | return -1; 29 | 30 | return ToString().CompareTo(other.ToString()); 31 | } 32 | 33 | public override bool Equals(object obj) 34 | { 35 | var other = obj as Version; 36 | 37 | if (other == null) 38 | { 39 | return false; 40 | } 41 | 42 | return ToString().Equals(other.ToString()); 43 | } 44 | 45 | public override int GetHashCode() 46 | { 47 | return ToString().GetHashCode(); 48 | } 49 | 50 | public override string ToString() 51 | { 52 | return $"{Major}.{Minor}"; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/ChromeDevToolsGeneratorCLI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/CliArguments.cs: -------------------------------------------------------------------------------- 1 | namespace ChromeDevToolsGeneratorCLI 2 | { 3 | using EntryPoint; 4 | 5 | public class CliArguments : BaseCliArguments 6 | { 7 | private const string DefaultChromeDebuggingProtocolSchemaPath = "chrome-debugging-protocol-schema.json"; 8 | private const string DefaultChromeDebuggingProtocolPath = "chrome-debugging-protocol.json"; 9 | 10 | private const string DefaultOutputPath = "./OutputProtocol"; 11 | private const string DefaultSettingsPath = "./Templates/settings.json"; 12 | private const string DefaultRootNamespace = "BaristaLabs.ChromeDevTools"; 13 | 14 | public CliArguments() 15 | : base("RemoteInterfaceCLI") 16 | { 17 | ProtocolPath = DefaultChromeDebuggingProtocolPath; 18 | ProtocolSchemaPath = DefaultChromeDebuggingProtocolSchemaPath; 19 | Settings = DefaultSettingsPath; 20 | OutputPath = DefaultOutputPath; 21 | } 22 | 23 | [Option(ShortName: 'f', 24 | LongName: "force-download")] 25 | [Help("Forces the Chrome Protocol Definition to be downloaded from source even if it already exists.")] 26 | public bool ForceDownload 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | [Option("force")] 33 | [Help("Forces the output directory to be overwritten")] 34 | public bool ForceOverwrite 35 | { 36 | get; 37 | set; 38 | } 39 | 40 | [Option("generate-protocol-schema")] 41 | [Help("Forces the protocol schema to be generated from the current class definition")] 42 | public bool GenerateProtocolSchema 43 | { 44 | get; 45 | set; 46 | } 47 | 48 | [OptionParameter(ShortName: 'o', 49 | LongName: "output-path")] 50 | [Help("Indicates the folder that will contain the generated class library [Default: ./OutputProtocol]")] 51 | public string OutputPath 52 | { 53 | get; 54 | set; 55 | } 56 | 57 | [OptionParameter(ShortName: 'p', 58 | LongName: "protocol-path")] 59 | [Help("Indicates the path to the Chrome Debugging Protocol JSON file to use. [Default: chrome-debugging-protocol.json]")] 60 | public string ProtocolPath 61 | { 62 | get; 63 | set; 64 | } 65 | 66 | [OptionParameter(ShortName: 'd', 67 | LongName: "protocol-schema-path")] 68 | [Help("Indicates the path to the Chrome Debugging Protocol JSON Schema file to use. [Default: chrome-debugging-protocol-schema.json]")] 69 | public string ProtocolSchemaPath 70 | { 71 | get; 72 | set; 73 | } 74 | 75 | [OptionParameter(ShortName: 's', 76 | LongName: "settings")] 77 | [Help("Indicates the path to the code generation settings file. [Default: ./Templates/settings.json]")] 78 | public string Settings 79 | { 80 | get; 81 | set; 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/PdlConverter.cs: -------------------------------------------------------------------------------- 1 | namespace ChromeDevToolsGeneratorCLI 2 | { 3 | using IronPython.Hosting; 4 | using Microsoft.Scripting.Hosting; 5 | using Newtonsoft.Json.Linq; 6 | 7 | /// 8 | /// Uses IronPython to convert the chromium protocol to other formats using the provided chromium pdl script 9 | /// 10 | /// 11 | /// https://cs.chromium.org/chromium/src/third_party/inspector_protocol/ 12 | /// 13 | public class PdlConverter 14 | { 15 | private ScriptEngine Engine 16 | { 17 | get; 18 | set; 19 | } 20 | 21 | private dynamic Scope 22 | { 23 | get; 24 | set; 25 | } 26 | 27 | public PdlConverter(string script) 28 | { 29 | Engine = Python.CreateEngine(); 30 | Scope = Engine.CreateScope(); 31 | Engine.Execute(script, Scope); 32 | } 33 | 34 | public JObject ToJson(string protocol, string fileName) 35 | { 36 | var parsedProtocol = Scope.loads(protocol, fileName); 37 | var json = Scope.json.dumps(parsedProtocol); 38 | 39 | return JObject.Parse(json.ToString()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Program.cs: -------------------------------------------------------------------------------- 1 | namespace ChromeDevToolsGeneratorCLI 2 | { 3 | using BaristaLabs.ChromeDevTools; 4 | using BaristaLabs.ChromeDevTools.RemoteInterface.CodeGen; 5 | using BaristaLabs.ChromeDevTools.RemoteInterface.ProtocolDefinition; 6 | using EntryPoint; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | using NJsonSchema; 11 | using NJsonSchema.Generation; 12 | using System; 13 | using System.IO; 14 | using System.Threading.Tasks; 15 | using System.Text; 16 | 17 | class Program 18 | { 19 | static int Main(string[] args) 20 | { 21 | var cliArguments = Cli.Parse(args); 22 | 23 | //Load Settings. 24 | if (!File.Exists(cliArguments.Settings)) 25 | throw new FileNotFoundException($"The specified settings file ({cliArguments.Settings}) could not be found. Please check that the settings file exists."); 26 | 27 | var settingsJson = File.ReadAllText(cliArguments.Settings); 28 | var settings = JsonConvert.DeserializeObject(settingsJson); 29 | 30 | // setup our DI 31 | var serviceProvider = new ServiceCollection() 32 | .AddCodeGenerationServices(settings) 33 | .BuildServiceProvider(); 34 | 35 | //Get the protocol Data. 36 | Console.WriteLine("Loading protocol definition..."); 37 | var protocolDefinitionData = GetProtocolDefinitionData(cliArguments).GetAwaiter().GetResult(); 38 | 39 | //Validate that the protocol data matches our current class object. 40 | Console.WriteLine("Validating protocol definition..."); 41 | var protocolSchema = GetProtocolDefinitionSchema(cliArguments).GetAwaiter().GetResult(); 42 | var errors = protocolSchema.Validate(protocolDefinitionData); 43 | if (errors.Count > 0) 44 | throw new InvalidOperationException("Protocol Definition data does not validate against Protocol Definition Class Library. Ensure that all properties have been added."); 45 | 46 | var protocolDefinition = protocolDefinitionData.ToObject(new JsonSerializer() { MetadataPropertyHandling = MetadataPropertyHandling.Ignore }); 47 | 48 | //Begin the code generation process. 49 | Console.WriteLine("Generating protocol definition code files..."); 50 | var protocolGenerator = serviceProvider.GetRequiredService>(); 51 | var codeFiles = protocolGenerator.GenerateCode(protocolDefinition, null); 52 | 53 | //Delete the output folder if force is specified and it exists... 54 | Console.WriteLine("Writing generated code files..."); 55 | if (Directory.Exists(cliArguments.OutputPath) && cliArguments.ForceOverwrite) 56 | { 57 | Console.WriteLine("Generating protocol definition project..."); 58 | Directory.Delete(cliArguments.OutputPath, true); 59 | } 60 | 61 | //Create the output path if it doesn't exist, and write generated files to disk. 62 | var directoryInfo = Directory.CreateDirectory(cliArguments.OutputPath); 63 | 64 | var sha1 = System.Security.Cryptography.SHA1.Create(); 65 | foreach (var codeFile in codeFiles) 66 | { 67 | var targetFilePath = Path.GetFullPath(Path.Combine(cliArguments.OutputPath, codeFile.Key)); 68 | Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath)); 69 | //Only update the file if the SHA1 hashes don't match 70 | if (File.Exists(targetFilePath)) 71 | { 72 | var targetFileHash = sha1.ComputeHash(File.ReadAllBytes(targetFilePath)); 73 | var codeFileHash = sha1.ComputeHash(Encoding.UTF8.GetBytes(codeFile.Value)); 74 | if (String.Compare(Convert.ToBase64String(targetFileHash), Convert.ToBase64String(codeFileHash)) != 0) 75 | { 76 | File.WriteAllText(targetFilePath, codeFile.Value); 77 | } 78 | } 79 | else 80 | { 81 | File.WriteAllText(targetFilePath, codeFile.Value); 82 | } 83 | } 84 | 85 | //Completed. 86 | Console.WriteLine("All done!"); 87 | return 0; 88 | } 89 | 90 | /// 91 | /// Returns a merged ProtocolDefinition JObject 92 | /// 93 | /// 94 | /// 95 | public static async Task GetProtocolDefinitionData(CliArguments args) 96 | { 97 | JObject protocolData; 98 | if (args.ForceDownload || !File.Exists(args.ProtocolPath)) 99 | { 100 | Console.WriteLine("Obtaining protocol definition from installed Chrome version..."); 101 | 102 | ChromeVersion currentVersion; 103 | using (var chrome = Chrome.OpenChrome()) 104 | { 105 | currentVersion = await chrome.GetChromeVersion(); 106 | } 107 | 108 | var browserProtocolPdl = await Chrome.GetBrowserProtocolForChromeVersion(currentVersion); 109 | var javaScriptProtocolPdl = await Chrome.GetJavaScriptProtocolForChromeVersion(currentVersion); 110 | 111 | var pdlScript = await Chrome.GetInspectorProtocolConverterPythonScript(currentVersion); 112 | 113 | var pdlConverter = new PdlConverter(pdlScript); 114 | var browserProtocol = pdlConverter.ToJson(browserProtocolPdl, "browser_protocol.pdl"); 115 | var jsProtocol = pdlConverter.ToJson(javaScriptProtocolPdl, "js_protocol.pdl"); 116 | 117 | protocolData = Chrome.MergeJavaScriptProtocolDefinitions(browserProtocol, jsProtocol); 118 | protocolData["chromeVersion"] = JToken.FromObject(currentVersion); 119 | File.WriteAllText(args.ProtocolPath, JsonConvert.SerializeObject(protocolData, Formatting.Indented)); 120 | } 121 | else 122 | { 123 | Console.WriteLine("Using previously obtained protocol definition..."); 124 | var protocolJson = File.ReadAllText(args.ProtocolPath); 125 | protocolData = JObject.Parse(protocolJson); 126 | } 127 | 128 | return protocolData; 129 | } 130 | 131 | public static async Task GetProtocolDefinitionSchema(CliArguments args) 132 | { 133 | JsonSchema4 protocolSchema; 134 | 135 | if (args.GenerateProtocolSchema || !File.Exists(args.ProtocolSchemaPath)) 136 | { 137 | Console.WriteLine("Generating protocol definition from current generator interface..."); 138 | 139 | protocolSchema = await JsonSchema4.FromTypeAsync(new JsonSchemaGeneratorSettings() { FlattenInheritanceHierarchy = true }); 140 | } 141 | else 142 | { 143 | Console.WriteLine("Using previously obtained protocol schema..."); 144 | protocolSchema = await JsonSchema4.FromFileAsync(args.ProtocolSchemaPath); 145 | } 146 | 147 | return protocolSchema; 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ChromeDevToolsGeneratorCLI": { 4 | "commandName": "Project", 5 | "commandLineArgs": "-o \"c:\\\\projects\\\\chrome-dev-tools-runtime\\\\BaristaLabs.ChromeDevTools.Runtime\"", 6 | "workingDirectory": "C:\\Projects\\chrome-dev-tools-generator\\src\\ChromeDevToolsGeneratorCLI" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/ChromeSession.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Collections.Concurrent; 8 | using System.Diagnostics; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Threading.Tasks.Dataflow; 12 | using WebSocket4Net; 13 | 14 | /// 15 | /// Represents a websocket connection to a running chrome instance that can be used to send commands and recieve events. 16 | /// 17 | public partial class ChromeSession : IDisposable 18 | { 19 | private readonly string m_endpointAddress; 20 | private readonly ILogger m_logger; 21 | private readonly ConcurrentDictionary>> m_eventHandlers = new ConcurrentDictionary>>(); 22 | private readonly ConcurrentDictionary m_eventTypeMap = new ConcurrentDictionary(); 23 | 24 | private ActionBlock m_messageQueue; 25 | private WebSocket m_sessionSocket; 26 | private ManualResetEventSlim m_openEvent = new ManualResetEventSlim(false); 27 | private ManualResetEventSlim m_responseReceived = new ManualResetEventSlim(false); 28 | private LastResponseInfo m_lastResponse; 29 | private long m_currentCommandId = 0; 30 | 31 | /// 32 | /// Gets or sets the number of milliseconds to wait for a command to complete. Default is 5 seconds. 33 | /// 34 | public int CommandTimeout 35 | { 36 | get; 37 | set; 38 | } 39 | 40 | /// 41 | /// Gets the endpoint address of the session. 42 | /// 43 | public string EndpointAddress 44 | { 45 | get { return m_endpointAddress; } 46 | } 47 | 48 | 49 | /// 50 | /// Creates a new Chrome session to the specified WS endpoint without logging. 51 | /// 52 | /// 53 | public ChromeSession(string endpointAddress) 54 | : this(null, endpointAddress) 55 | { 56 | } 57 | 58 | /// 59 | /// Creates a new ChromeSession to the specified WS endpoint with the specified logger implementation. 60 | /// 61 | /// 62 | /// 63 | public ChromeSession(ILogger logger, string endpointAddress) 64 | : this() 65 | { 66 | if (String.IsNullOrWhiteSpace(endpointAddress)) 67 | throw new ArgumentNullException(nameof(endpointAddress)); 68 | 69 | CommandTimeout = 5000; 70 | m_logger = logger; 71 | m_endpointAddress = endpointAddress; 72 | 73 | m_messageQueue = new ActionBlock((Action)ProcessIncomingMessage, 74 | new ExecutionDataflowBlockOptions { 75 | EnsureOrdered = true, 76 | MaxDegreeOfParallelism = 1, 77 | }); 78 | 79 | m_sessionSocket = new WebSocket(m_endpointAddress) 80 | { 81 | EnableAutoSendPing = false 82 | }; 83 | m_sessionSocket.MessageReceived += Ws_MessageReceived; 84 | m_sessionSocket.Error += Ws_Error; 85 | m_sessionSocket.Opened += Ws_Opened; 86 | } 87 | 88 | /// 89 | /// Sends the specified command and returns the associated command response. 90 | /// 91 | /// 92 | /// 93 | /// 94 | /// 95 | /// 96 | /// 97 | public async Task> SendCommand(TCommand command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true) 98 | where TCommand : ICommand 99 | { 100 | if (command == null) 101 | throw new ArgumentNullException(nameof(command)); 102 | 103 | var result = await SendCommand(command.CommandName, JToken.FromObject(command), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived); 104 | 105 | if (result == null) 106 | return null; 107 | 108 | if (!CommandResponseTypeMap.TryGetCommandResponseType(out Type commandResponseType)) 109 | throw new InvalidOperationException($"Type {typeof(TCommand)} does not correspond to a known command response type."); 110 | 111 | return result.ToObject(commandResponseType) as ICommandResponse; 112 | } 113 | 114 | /// 115 | /// Sends the specified command and returns the associated command response. 116 | /// 117 | /// 118 | /// 119 | /// 120 | /// 121 | /// 122 | /// 123 | /// 124 | public async Task SendCommand(TCommand command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true) 125 | where TCommand : ICommand 126 | where TCommandResponse : ICommandResponse 127 | { 128 | if (command == null) 129 | throw new ArgumentNullException(nameof(command)); 130 | 131 | var result = await SendCommand(command.CommandName, JToken.FromObject(command), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived); 132 | 133 | if (result == null) 134 | return default(TCommandResponse); 135 | 136 | return result.ToObject(); 137 | } 138 | 139 | /// 140 | /// Returns a JToken based on a command created with the specified command name and params. 141 | /// 142 | /// 143 | /// 144 | /// 145 | /// 146 | /// 147 | /// 148 | [DebuggerStepThrough] 149 | public async Task SendCommand(string commandName, JToken @params, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true) 150 | { 151 | var message = new 152 | { 153 | id = Interlocked.Increment(ref m_currentCommandId), 154 | method = commandName, 155 | @params = @params 156 | }; 157 | 158 | if (millisecondsTimeout.HasValue == false) 159 | millisecondsTimeout = CommandTimeout; 160 | 161 | await OpenSessionConnection(cancellationToken); 162 | 163 | LogTrace("Sending {id} {method}: {params}", message.id, message.method, @params.ToString()); 164 | 165 | var contents = JsonConvert.SerializeObject(message); 166 | 167 | m_responseReceived.Reset(); 168 | m_sessionSocket.Send(contents); 169 | 170 | var responseWasReceived = await Task.Run(() => m_responseReceived.Wait(millisecondsTimeout.Value, cancellationToken)); 171 | 172 | if (!responseWasReceived && throwExceptionIfResponseNotReceived) 173 | throw new InvalidOperationException($"A command response was not received: {commandName}"); 174 | 175 | if (m_lastResponse.IsError) 176 | { 177 | var errorMessage = m_lastResponse.Result.Value("message"); 178 | var errorData = m_lastResponse.Result.Value("data"); 179 | 180 | var exceptionMessage = $"{commandName}: {errorMessage}"; 181 | if (!String.IsNullOrWhiteSpace(errorData)) 182 | exceptionMessage = $"{exceptionMessage} - {errorData}"; 183 | 184 | LogTrace("Recieved Error Response {id}: {message} {data}", message.id, message, errorData); 185 | throw new CommandResponseException(exceptionMessage) 186 | { 187 | Code = m_lastResponse.Result.Value("code") 188 | }; 189 | } 190 | return m_lastResponse.Result; 191 | } 192 | 193 | /// 194 | /// Subscribes to the event associated with the given type. 195 | /// 196 | /// Event to subscribe to 197 | /// 198 | public void Subscribe(Action eventCallback) 199 | where TEvent : IEvent 200 | { 201 | if (eventCallback == null) 202 | throw new ArgumentNullException(nameof(eventCallback)); 203 | 204 | var eventName = m_eventTypeMap.GetOrAdd( 205 | typeof(TEvent), 206 | (type) => 207 | { 208 | if (!EventTypeMap.TryGetMethodNameForType(out string methodName)) 209 | throw new InvalidOperationException($"Type {typeof(TEvent)} does not correspond to a known event type."); 210 | 211 | return methodName; 212 | }); 213 | 214 | var callbackWrapper = new Action(obj => eventCallback((TEvent)obj)); 215 | m_eventHandlers.AddOrUpdate(eventName, 216 | (m) => new ConcurrentBag>(new[] { callbackWrapper }), 217 | (m, currentBag) => 218 | { 219 | currentBag.Add(callbackWrapper); 220 | return currentBag; 221 | }); 222 | } 223 | 224 | private async Task OpenSessionConnection(CancellationToken cancellationToken) 225 | { 226 | if (m_sessionSocket.State != WebSocketState.Open) 227 | { 228 | m_sessionSocket.Open(); 229 | 230 | await Task.Run(() => m_openEvent.Wait(cancellationToken)); 231 | } 232 | } 233 | 234 | private void RaiseEvent(string methodName, JToken eventData) 235 | { 236 | if (m_eventHandlers.TryGetValue(methodName, out ConcurrentBag> bag)) 237 | { 238 | if (!EventTypeMap.TryGetTypeForMethodName(methodName, out Type eventType)) 239 | throw new InvalidOperationException($"Unknown {methodName} does not correspond to a known event type."); 240 | 241 | var typedEventData = eventData.ToObject(eventType); 242 | foreach (var callback in bag) 243 | { 244 | callback(typedEventData); 245 | } 246 | } 247 | } 248 | 249 | private void ProcessIncomingMessage(string message) 250 | { 251 | var messageObject = JObject.Parse(message); 252 | 253 | long commandId; 254 | if (messageObject.TryGetValue("id", out JToken idProperty) && (commandId = idProperty.Value()) == m_currentCommandId) 255 | { 256 | 257 | if (messageObject.TryGetValue("error", out JToken errorProperty)) 258 | { 259 | m_lastResponse = new LastResponseInfo 260 | { 261 | IsError = true, 262 | Result = errorProperty 263 | }; 264 | m_responseReceived.Set(); 265 | return; 266 | } 267 | 268 | m_lastResponse = new LastResponseInfo 269 | { 270 | Result = messageObject["result"] 271 | }; 272 | 273 | LogTrace("Recieved Response {id}: {message}", commandId, m_lastResponse.Result.ToString()); 274 | m_responseReceived.Set(); 275 | return; 276 | } 277 | 278 | if (messageObject.TryGetValue("method", out JToken methodProperty)) 279 | { 280 | var method = methodProperty.Value(); 281 | var eventData = messageObject["params"]; 282 | LogTrace("Recieved Event {method}: {params}", method, eventData.ToString()); 283 | RaiseEvent(method, eventData); 284 | return; 285 | } 286 | 287 | LogTrace("Recieved Other: {message}", message); 288 | } 289 | 290 | private void LogTrace(string message, params object[] args) 291 | { 292 | if (m_logger == null) 293 | return; 294 | 295 | m_logger.LogTrace(message, args); 296 | } 297 | 298 | private void LogError(string message, params object[] args) 299 | { 300 | if (m_logger == null) 301 | return; 302 | 303 | m_logger.LogError(message, args); 304 | } 305 | 306 | 307 | #region EventHandlers 308 | private void Ws_Opened(object sender, EventArgs e) 309 | { 310 | m_openEvent.Set(); 311 | } 312 | 313 | private void Ws_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e) 314 | { 315 | LogError("Error: {exception}", e.Exception); 316 | throw e.Exception; 317 | } 318 | 319 | private void Ws_MessageReceived(object sender, MessageReceivedEventArgs e) 320 | { 321 | //Add incoming messages to an ActionBlock so they can be processed sequentially. 322 | if (m_messageQueue != null) 323 | { 324 | m_messageQueue.Post(e.Message); 325 | } 326 | } 327 | #endregion 328 | 329 | #region IDisposable Support 330 | private bool m_isDisposed = false; 331 | 332 | private void Dispose(bool disposing) 333 | { 334 | if (!m_isDisposed) 335 | { 336 | if (disposing) 337 | { 338 | //Clear all subscribed events. 339 | m_eventHandlers.Clear(); 340 | m_eventTypeMap.Clear(); 341 | 342 | if (m_sessionSocket != null) 343 | { 344 | m_sessionSocket.Opened -= Ws_Opened; 345 | m_sessionSocket.Error -= Ws_Error; 346 | m_sessionSocket.MessageReceived -= Ws_MessageReceived; 347 | m_sessionSocket.Dispose(); 348 | m_sessionSocket = null; 349 | } 350 | 351 | if (m_messageQueue != null) 352 | { 353 | m_messageQueue.Complete(); 354 | m_messageQueue = null; 355 | } 356 | 357 | if (m_openEvent != null) 358 | { 359 | m_openEvent.Dispose(); 360 | m_openEvent = null; 361 | } 362 | 363 | if (m_responseReceived != null) 364 | { 365 | m_responseReceived.Dispose(); 366 | m_responseReceived = null; 367 | } 368 | } 369 | 370 | m_isDisposed = true; 371 | } 372 | } 373 | 374 | /// 375 | /// Disposes of the ChromeSession and frees all resources. 376 | /// 377 | public void Dispose() 378 | { 379 | Dispose(true); 380 | } 381 | #endregion 382 | 383 | #region Nested Classes 384 | private class LastResponseInfo 385 | { 386 | public bool IsError = false; 387 | public JToken Result; 388 | } 389 | #endregion 390 | } 391 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/ChromeSession_Domains.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | using System; 4 | 5 | public partial class ChromeSession 6 | { 7 | {{#each domains}} 8 | private Lazy<{{dehumanize Name}}.{{dehumanize Name}}Adapter> m_{{dehumanize Name}}; 9 | {{/each}} 10 | 11 | public ChromeSession() 12 | { 13 | {{#each domains}} 14 | m_{{dehumanize Name}} = new Lazy<{{dehumanize Name}}.{{dehumanize Name}}Adapter>(() => new {{dehumanize Name}}.{{dehumanize Name}}Adapter(this)); 15 | {{/each}} 16 | } 17 | 18 | {{#each domains}} 19 | /// 20 | /// Gets the adapter for the {{Name}} domain. 21 | /// 22 | public {{dehumanize Name}}.{{dehumanize Name}}Adapter {{dehumanize Name}} 23 | { 24 | get { return m_{{dehumanize Name}}.Value; } 25 | } 26 | 27 | {{/each}} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/CommandResponseException.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents an error generated by a command. 7 | /// 8 | [Serializable] 9 | public class CommandResponseException : Exception 10 | { 11 | public long Code 12 | { 13 | get; 14 | set; 15 | } 16 | 17 | public CommandResponseException() 18 | { 19 | } 20 | 21 | public CommandResponseException(string message) 22 | : base(message) 23 | { 24 | } 25 | 26 | public CommandResponseException(string message, Exception innerException) 27 | : base(message, innerException) 28 | { 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/CommandResponseExtensions.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | public static class ICommandResponseExtensions 4 | { 5 | public static TCommandResponse GetResponse(this ICommandResponse response) 6 | where TCommandResponse : class, ICommandResponse 7 | { 8 | return response as TCommandResponse; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/CommandResponseTypeMap.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public static class CommandResponseTypeMap 7 | { 8 | private readonly static IDictionary s_commandResponseTypeDictionary; 9 | 10 | static CommandResponseTypeMap() 11 | { 12 | s_commandResponseTypeDictionary = new Dictionary() 13 | { 14 | {{#each commands}} 15 | { typeof({{FullTypeName}}), typeof({{FullTypeName}}Response) }, 16 | {{/each}} 17 | }; 18 | } 19 | 20 | /// 21 | /// Gets the command response type corresponding to the specified command type 22 | /// 23 | public static bool TryGetCommandResponseType(out Type commandResponseType) 24 | where T : ICommand 25 | { 26 | return s_commandResponseTypeDictionary.TryGetValue(typeof(T), out commandResponseType); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/EventTypeMap.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public static class EventTypeMap 7 | { 8 | private readonly static IDictionary s_methodNameEventTypeDictionary; 9 | private readonly static IDictionary s_eventTypeMethodNameDictionary; 10 | 11 | static EventTypeMap() 12 | { 13 | s_methodNameEventTypeDictionary = new Dictionary() 14 | { 15 | {{#each events}} 16 | { "{{EventName}}", typeof({{FullTypeName}}) }, 17 | {{/each}} 18 | }; 19 | 20 | s_eventTypeMethodNameDictionary = new Dictionary() 21 | { 22 | {{#each events}} 23 | { typeof({{FullTypeName}}), "{{EventName}}" }, 24 | {{/each}} 25 | }; 26 | } 27 | 28 | /// 29 | /// Gets the event type corresponding to the specified method name. 30 | /// 31 | public static bool TryGetTypeForMethodName(string methodName, out Type eventType) 32 | { 33 | return s_methodNameEventTypeDictionary.TryGetValue(methodName, out eventType); 34 | } 35 | 36 | /// 37 | /// Gets the method name corresponding to the specified event type. 38 | /// 39 | public static bool TryGetMethodNameForType(out string methodName) 40 | where TEvent : IEvent 41 | { 42 | return s_eventTypeMethodNameDictionary.TryGetValue(typeof(TEvent), out methodName); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/ICommand.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | /// 4 | /// Represents a command used by the Chrome Remote Interface 5 | /// 6 | public interface ICommand 7 | { 8 | /// 9 | /// Gets the name of the command. 10 | /// 11 | string CommandName 12 | { 13 | get; 14 | } 15 | } 16 | 17 | /// 18 | /// Represents a response to a command submitted by the Chrome Remote Interface 19 | /// 20 | public interface ICommandResponse 21 | { 22 | } 23 | 24 | /// 25 | /// Represents a response to a command submitted by the Chrome Remote Interface 26 | /// 27 | public interface ICommandResponse : ICommandResponse 28 | where T : ICommand 29 | { 30 | } 31 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/IEvent.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}} 2 | { 3 | /// 4 | /// Represents an event raised by the Chrome Remote Interface 5 | /// 6 | public interface IEvent 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/command.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}}.{{domain.Name}} 2 | { 3 | using Newtonsoft.Json; 4 | 5 | /// 6 | /// {{xml-code-comment command.Description 1}} 7 | /// 8 | public sealed class {{className}}Command : ICommand 9 | { 10 | private const string ChromeRemoteInterface_CommandName = "{{domain.Name}}.{{command.Name}}"; 11 | 12 | [JsonIgnore] 13 | public string CommandName 14 | { 15 | get { return ChromeRemoteInterface_CommandName; } 16 | } 17 | 18 | {{#each command.Parameters}} 19 | {{#if Description}} 20 | /// 21 | /// {{xml-code-comment Description 2}} 22 | /// 23 | {{else}} 24 | /// 25 | /// Gets or sets the {{Name}} 26 | /// 27 | {{/if}} 28 | [JsonProperty("{{Name}}"{{#if Optional}}, DefaultValueHandling = DefaultValueHandling.Ignore{{/if}})] 29 | public {{typemap ../context}} {{dehumanize Name}} 30 | { 31 | get; 32 | set; 33 | } 34 | {{/each}} 35 | } 36 | 37 | public sealed class {{className}}CommandResponse : ICommandResponse<{{className}}Command> 38 | { 39 | {{#each command.Returns}} 40 | {{#if Description}} 41 | /// 42 | /// {{xml-code-comment Description 2}} 43 | /// 44 | {{else}} 45 | /// 46 | /// Gets or sets the {{Name}} 47 | /// 48 | {{/if}} 49 | [JsonProperty("{{Name}}"{{#if Optional}}, DefaultValueHandling = DefaultValueHandling.Ignore{{/if}})] 50 | public {{typemap ../context}} {{dehumanize Name}} 51 | { 52 | get; 53 | set; 54 | } 55 | {{/each}} 56 | } 57 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/domain.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}}.{{domain.Name}} 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | /// 8 | /// Represents an adapter for the {{domain.Name}} domain to simplify the command interface. 9 | /// 10 | public class {{dehumanize domain.Name}}Adapter 11 | { 12 | private readonly ChromeSession m_session; 13 | 14 | public {{dehumanize domain.Name}}Adapter(ChromeSession session) 15 | { 16 | m_session = session ?? throw new ArgumentNullException(nameof(session)); 17 | } 18 | 19 | /// 20 | /// Gets the ChromeSession associated with the adapter. 21 | /// 22 | public ChromeSession Session 23 | { 24 | get { return m_session; } 25 | } 26 | 27 | {{#each domain.Commands}} 28 | /// 29 | /// {{xml-code-comment Description 2}} 30 | /// 31 | public async Task<{{dehumanize Name}}CommandResponse> {{dehumanize Name}}({{dehumanize Name}}Command command{{#if NoParameters}} = null{{/if}}, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true) 32 | { 33 | return await m_session.SendCommand<{{dehumanize Name}}Command, {{dehumanize Name}}CommandResponse>(command{{#if NoParameters}} ?? new {{dehumanize Name}}Command(){{/if}}, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived); 34 | } 35 | {{/each}} 36 | 37 | {{#each domain.Events}} 38 | /// 39 | /// {{xml-code-comment Description 2}} 40 | /// 41 | public void SubscribeTo{{dehumanize Name}}Event(Action<{{dehumanize Name}}Event> eventCallback) 42 | { 43 | m_session.Subscribe(eventCallback); 44 | } 45 | {{/each}} 46 | } 47 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/event.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}}.{{domain.Name}} 2 | { 3 | using Newtonsoft.Json; 4 | 5 | /// 6 | /// {{xml-code-comment event.Description 1}} 7 | /// 8 | public sealed class {{className}}Event : IEvent 9 | { 10 | {{#each event.Parameters}} 11 | {{#if Description}} 12 | /// 13 | /// {{xml-code-comment Description 2}} 14 | /// 15 | {{else}} 16 | /// 17 | /// Gets or sets the {{Name}} 18 | /// 19 | {{/if}} 20 | [JsonProperty("{{Name}}"{{#if Optional}}, DefaultValueHandling = DefaultValueHandling.Ignore{{/if}})] 21 | public {{typemap ../context}} {{dehumanize Name}} 22 | { 23 | get; 24 | set; 25 | } 26 | {{/each}} 27 | } 28 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/project.hbs: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard1.3 4 | true 5 | {{chromeVersion.BrowserVersion}}{{#if runtimeVersion}}-{{runtimeVersion}}{{/if}} 6 | Sean McLellan 7 | BaristaLabs, LLC 8 | https://github.com/BaristaLabs/chrome-dev-tools-runtime.git 9 | Git 10 | ChromeDevTools, .Net Core 11 | https://raw.githubusercontent.com/BaristaLabs/chrome-dev-tools-runtime/master/LICENSE 12 | Copyright (c) 2017 BaristaLabs, LLC 13 | https://github.com/BaristaLabs/chrome-dev-tools-runtime 14 | .Net Core based runtime to interact with an running instance of Chrome via the ChromeDevTools protocol. 15 | Chrome Version: {{chromeVersion.BrowserVersion}}; Protocol Version: {{chromeVersion.ProtocolVersion}};{{#if runtimeVersion}} Runtime Version: {{runtimeVersion}};{{/if}} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | { 4 | "templatePath": "project.hbs", 5 | "outputPath": "BaristaLabs.ChromeDevTools.Runtime.csproj" 6 | }, 7 | { 8 | "templatePath": "ChromeSession.hbs", 9 | "outputPath": "ChromeSession.cs" 10 | }, 11 | { 12 | "templatePath": "ChromeSession_Domains.hbs", 13 | "outputPath": "ChromeSession_Domains.cs" 14 | }, 15 | { 16 | "templatePath": "CommandResponseException.hbs", 17 | "outputPath": "CommandResponseException.cs" 18 | }, 19 | { 20 | "templatePath": "CommandResponseExtensions.hbs", 21 | "outputPath": "CommandResponseExtensions.cs" 22 | }, 23 | { 24 | "templatePath": "CommandResponseTypeMap.hbs", 25 | "outputPath": "CommandResponseTypeMap.cs" 26 | }, 27 | { 28 | "templatePath": "EventTypeMap.hbs", 29 | "outputPath": "EventTypeMap.cs" 30 | }, 31 | { 32 | "templatePath": "ICommand.hbs", 33 | "outputPath": "ICommand.cs" 34 | }, 35 | { 36 | "templatePath": "IEvent.hbs", 37 | "outputPath": "IEvent.cs" 38 | } 39 | ], 40 | "rootNamespace": "BaristaLabs.ChromeDevTools.Runtime", 41 | "runtimeVersion": "" 42 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/type-enum.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}}.{{domain.Name}} 2 | { 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using System.Runtime.Serialization; 6 | 7 | /// 8 | /// {{xml-code-comment type.Description 1}} 9 | /// 10 | [JsonConverter(typeof(StringEnumConverter))] 11 | public enum {{className}} 12 | { 13 | {{#each type.Enum}} 14 | [EnumMember(Value = "{{this}}")] 15 | {{dehumanize this}}, 16 | {{/each}} 17 | } 18 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/type-hash.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}}.{{domain.Name}} 2 | { 3 | using System.Collections.Generic; 4 | 5 | /// 6 | /// {{xml-code-comment type.Description 1}} 7 | /// 8 | public sealed class {{className}} : Dictionary 9 | { 10 | {{#each type.Properties}} 11 | /// 12 | /// {{xml-code-comment Description 2}} 13 | /// 14 | [JsonProperty("{{Name}}"{{#if Optional}}, DefaultValueHandling = DefaultValueHandling.Ignore{{/if}})] 15 | public {{typemap ../context}} {{dehumanize Name}} 16 | { 17 | get; 18 | set; 19 | } 20 | {{/each}} 21 | } 22 | } -------------------------------------------------------------------------------- /src/ChromeDevToolsGeneratorCLI/Templates/type-object.hbs: -------------------------------------------------------------------------------- 1 | namespace {{rootNamespace}}.{{domain.Name}} 2 | { 3 | using Newtonsoft.Json; 4 | 5 | /// 6 | /// {{xml-code-comment type.Description 1}} 7 | /// 8 | public sealed class {{className}} 9 | { 10 | {{#each type.Properties}} 11 | /// 12 | /// {{xml-code-comment Description 2}} 13 | /// 14 | [JsonProperty("{{Name}}"{{#if Optional}}, DefaultValueHandling = DefaultValueHandling.Ignore{{/if}})] 15 | public {{typemap ../context}} {{dehumanize Name}} 16 | { 17 | get; 18 | set; 19 | } 20 | {{/each}} 21 | } 22 | } --------------------------------------------------------------------------------