diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 3af0beb6c..8ff4bca75 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,12 +1,3 @@ # These are supported funding model platforms -github: hardkoded -patreon: # Replace with a single Patreon username -open_collective: hardkoded-projects -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +github: amaitland \ No newline at end of file diff --git a/.github/workflows/on-push-do-docs.yml b/.github/workflows/on-push-do-docs.yml deleted file mode 100644 index 84eb62b4c..000000000 --- a/.github/workflows/on-push-do-docs.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: on-push-do-docs -on: - push: -jobs: - docs: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - name: Run MarkdownSnippets - run: | - dotnet tool install --global MarkdownSnippets.Tool - mdsnippets ${GITHUB_WORKSPACE} - shell: bash - - name: Push changes - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git commit -m "Docs changes" -a || echo "nothing to commit" - remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git" - branch="${GITHUB_REF:11}" - git push "${remote}" ${branch} || echo "nothing to push" - shell: bash \ No newline at end of file diff --git a/.gitignore b/.gitignore index 015a8f464..8159459a2 100644 --- a/.gitignore +++ b/.gitignore @@ -299,3 +299,4 @@ samples/PupppeterSharpAspNetFrameworkSample/PupppeterSharpAspNetFrameworkSample/ # Visual Studio Code user config directory .vscode/ +/lib/PuppeteerSharp.Tests/Screenshots/golden-chromium/test.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3fdcca1c..acc27fd64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,9 @@ # How to Contribute -If you are interested in contributing to Puppeteer Sharp, Thank you! +If you are interested in contributing to CefSharp.Dom, Thank you! -Coding is not for lonely wolves. Welcome to the pack! - -The project has a clear roadmap we want to follow. If you want to contribute, ask before submitting a PR. We will analyze if it’s the right moment to implement that feature or not. -If you don’t know what to do, ASK! We have many many things to implement :) +If you want to contribute, ask before submitting a PR. +If you don’t know what to do, ASK! ## Getting setup @@ -20,13 +18,10 @@ You can run this PowerShell script on Windows ``` New-SelfSignedCertificate -Subject "localhost" -FriendlyName "Puppeteer" -CertStoreLocation "cert:\CurrentUser\My" -Get-ChildItem -Path cert:\CurrentUSer\my | where { $_.friendlyname -eq "Puppeteer" } | Export-Certificate -FilePath C:\projects\puppeteer-sharp\lib\PuppeteerSharp.TestServer\testCert.cer +Get-ChildItem -Path cert:\CurrentUSer\my | where { $_.friendlyname -eq "Puppeteer" } | Export-Certificate -FilePath C:\Projects\CefSharp.Dom\lib\PuppeteerSharp.TestServer\testCert.cer ``` -On MacOS, you can create an “SSL Server” certificate using the Keychain Access app and then export it to `.\lib\PuppeteerSharp.TestServer\testCert.cer` - - ## Code reviews All submissions, including submissions by project members, require review. We diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index dcc9826b6..e63a827b5 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,11 +1,11 @@ -## Looking for some help? Get priority support by [sponsoring the project](https://github.com/sponsors/hardkoded)/ +## Looking for some help? Get priority support by [sponsoring the project](https://github.com/sponsors/amaitland)/ **Before you file a bug, have you:** -* Tried upgrading to newest version of PuppeteerSharp, to see if your issue has already been resolved and released? -* Checked existing open *and* closed [issues](https://github.com/kblok/puppeteer-sharp/issues?utf8=%E2%9C%93&q=is%3Aissue), to see if the issue has already been reported? +* Tried upgrading to newest version of CefSharp.Dom, to see if your issue has already been resolved and released? +* Checked existing open *and* closed issues, to see if the issue has already been reported? * Tried reproducing your problem in a new isolated project? -* Considered if this is a general question and not a bug?. For general questions please use [StackOverflow](https://stackoverflow.com/search?q=puppeteer-sharp). +* Considered if this is a general question and not a bug?. For general questions please use Discussions. ### Description @@ -20,10 +20,7 @@ E.g. ```csharp // Arrange -var options = new LaunchOptions { /* */ }; -var chromiumRevision = BrowserFetcher.DefaultRevision; -var browser = await Puppeteer.LaunchAsync(options, chromiumRevision); -var page = browser.NewPageAsync(); +var devtoolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync(); // Act ... @@ -42,12 +39,12 @@ var page = browser.NewPageAsync(); ### Versions -* Which version of PuppeteerSharp are you using? -* Which .NET runtime and version are you targeting? E.g. .NET framework 4.6.1 or .NET Core 2.0. +* Which version of CefSharp.Dom are you using? +* Which .NET runtime and version are you targeting? E.g. .NET framework 4.6.1 or .NET Core 3.1. ### Additional Information Any additional information, configuration or data that might be necessary to reproduce the issue. - + diff --git a/LICENSE b/LICENSE index 977830f89..ec2fe73b5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2017 Darío Kondratiuk +Copyright (c) 2021 Alex Maitland Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a37841bb1..44e0bcaf7 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,184 @@ -# Puppeteer Sharp +# CefSharp.Dom (Formerly CefSharp.Puppeteer) -[![NuGet](https://buildstats.info/nuget/PuppeteerSharp)][NugetUrl] -[![Build status](https://ci.appveyor.com/api/projects/status/pwfkjb0c4jfdo7lc/branch/master?svg=true&pendingText=master&failingText=master&passingText=master)][BuildUrl] -[![Demo build status](https://ci.appveyor.com/api/projects/status/10g64a4aa0083wgf/branch/master?svg=true&pendingText=demo&failingText=demo&passingText=demo)][BuildDemoUrl] -[![CodeFactor](https://www.codefactor.io/repository/github/hardkoded/puppeteer-sharp/badge)][CodeFactorUrl] -[![Backers](https://opencollective.com/hardkoded-projects/backers/badge.svg)][Backers] -[![xs:code](https://img.shields.io/static/v1?logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iNDUiIHZpZXdCb3g9IjAgMCAzMCA0NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIwLjgzMjEgMzcuNDg4OVYyOS40NzJDMjAuODMyMSAyNi43NjQ1IDE5LjI5NjggMjQuNjY5MiAxNi44NTMxIDI0Ljk1NDJWMjAuMTgzMUMxOS4yOTY4IDIwLjQ2MjggMjAuODMyMSAxOC4zNTcgMjAuODMyMSAxNS42NDQyVjcuNjI3MjhDMjAuODMyMSAwLjI5MTE2NiAyNS4xMjQ2IDAuODEzNjY1IDI5LjM3NDYgMC44MTM2NjVWNC44MDM2NkMyNi45MzA5IDQuNTIzOTQgMjUuMzk1NiA0LjkxNDUgMjUuMzk1NiA3LjYyNzI4VjE1LjY0NDJDMjUuMzk1NiAxOC43NDc2IDI0LjQyODcgMjEuMTE3MyAyMi4zODM0IDIyLjUzMTdDMjQuNDI4NyAyMy45OTg5IDI1LjM5NTYgMjYuMzY4NyAyNS4zOTU2IDI5LjQ3MlYzNy40ODg5QzI1LjM5NTYgNDAuMTk2NCAyNi45MzA5IDQwLjU5MjMgMjkuMzc0NiA0MC4zMTI2VjQ0LjYwMzRDMjUuNjkzMSA0NC41OTgxIDIwLjgzMjEgNDQuODI1MSAyMC44MzIxIDM3LjQ4ODlaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMC41MDY0MDkgNDQuNTk4MVY0MC4zMDczQzIuOTUwMTYgNDAuNTg3IDQuNDg1NDcgNDAuMTk2NCA0LjQ4NTQ3IDM3LjQ4MzZWMjkuNDcyQzQuNDg1NDcgMjYuMzY4NiA1LjQ1MjM1IDIzLjk5ODkgNy40OTc2NiAyMi41MzE3QzUuNDUyMzUgMjEuMTIyNSA0LjQ4NTQ3IDE4Ljc0NzUgNC40ODU0NyAxNS42NDQyVjcuNjI3MjVDNC40ODU0NyA0LjkxOTc1IDIuOTUwMTYgNC41MjM5MiAwLjUwNjQwOSA0LjgwMzY0VjAuODEzNjM4QzQuNzU2NDEgMC44MTM2MzggOS4wNDg5MSAwLjI4NTg2IDkuMDQ4OTEgNy42MjcyNVYxNS42NDQyQzkuMDQ4OTEgMTguMzUxNyAxMC41ODQyIDIwLjQ2MjggMTMuMDI4IDIwLjE4MzFWMjQuOTU0MkMxMC41ODQyIDI0LjY3NDUgOS4wNDg5MSAyNi43NjQ1IDkuMDQ4OTEgMjkuNDcyVjM3LjQ4ODlDOS4wNDg5MSA0NC44MjUgNC4xOTMyOCA0NC41OTgxIDAuNTA2NDA5IDQ0LjU5ODFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K&message=xs:code&label=Ping+me+on&color=%23007EFF)](https://xscode.com/profile/kblok) +[![Nuget](https://img.shields.io/nuget/v/CefSharp.Dom?style=for-the-badge)](https://www.nuget.org/packages/CefSharp.Dom/) +[![AppVeyor](https://img.shields.io/appveyor/build/cefsharp/cefsharp-dom?style=for-the-badge)](https://ci.appveyor.com/project/cefsharp/cefsharp-dom) +[![AppVeyor tests](https://img.shields.io/appveyor/tests/cefsharp/cefsharp-dom?style=for-the-badge)](https://ci.appveyor.com/project/cefsharp/cefsharp-dom/build/tests) +[![GitHub](https://img.shields.io/github/license/cefsharp/CefSharp.Dom?style=for-the-badge)](https://github.com/cefsharp/CefSharp.Dom/blob/main/LICENSE) -[NugetUrl]: https://www.nuget.org/packages/PuppeteerSharp/ -[BuildUrl]: https://ci.appveyor.com/project/kblok/puppeteer-sharp/branch/master -[BuildDemoUrl]: https://ci.appveyor.com/project/kblok/puppeteer-sharp-0c8w9/branch/master -[CodeFactorUrl]: https://www.codefactor.io/repository/github/hardkoded/puppeteer-sharp -[Backers]: https://opencollective.com/hardkoded-projects +CefSharp.Dom is a fork of [puppeteer-sharp by Darío Kondratiuk](https://github.com/hardkoded/puppeteer-sharp) that has been adapted specifically for use with CefSharp. +- Strongly typed async DOM API +- Direct communication with the ChromiumWebBrowser instance rather than opening a web socket. +- 1:1 mapping of DevToolsContext and ChromiumWebBrowser +- CEF only supports a subset of features, features will be added/removed as the project matures +# Prerequisites -Puppeteer Sharp is a .NET port of the official [Node.JS Puppeteer API](https://github.com/puppeteer/puppeteer). + * .Net 4.7.2 or .Net Core 3.1 or greater + * CefSharp 104.4.180 or greater -# Puppeteer-Sharp 3 is here! +# Questions and Support -Check out the [blog post](https://www.hardkoded.com/blog/puppeteer-sharp-3-is-here)! +If you have an issue or a question: -# Useful links +* Ask a question on [Discussions](https://github.com/cefsharp/CefSharp.Dom/discussions). -* [API Documentation](http://www.puppeteersharp.com/api/index.html) -* Slack channel [#puppeteer-sharp](https://www.hardkoded.com/goto/pptr-slack) -* [StackOverflow](https://stackoverflow.com/search?q=puppeteer-sharp) -* [Issues](https://github.com/hardkoded/puppeteer-sharp/issues?utf8=%E2%9C%93&q=is%3Aissue) +## Contributing Guide -# Prerequisites +See [this document](CONTRIBUTING.md) for information on how to contribute. - * As Puppeteer-Sharp is a NetStandard 2.0 library, the minimum platform versions are .NET Framework 4.6.1 and .NET Core 2.0. [Read more](https://docs.microsoft.com/en-us/dotnet/standard/net-standard). - * The minimum **Windows** versions supporting the WebSocket library are Windows 8 and Windows Server 2012. [Read more](https://docs.microsoft.com/en-us/dotnet/api/system.net.websockets?redirectedfrom=MSDN&view=netframework-4.7.2). If you need to run Puppeteer-Sharp on Windows 7 you can use [System.Net.WebSockets.Client.Managed](https://www.nuget.org/packages/System.Net.WebSockets.Client.Managed/) through the [LaunchOptions.WebSocketFactory](https://www.puppeteersharp.com/api/PuppeteerSharp.LaunchOptions.html#PuppeteerSharp_LaunchOptions_WebSocketFactory) property. - * If you have issues running Chrome on Linux, the Puppeteer repo has a [great troubleshooting guide](https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md). - * X-server is required on Linux. +# Usage - # How to Contribute and Provide Feedback +## DevToolsContext -Some of the best ways to contribute are to try things out file bugs and fix issues. +The **DevToolsContext** class is the main entry point into the library and can be created from a +ChromiumWebBrowser instance. +Only a **single** DevToolsContext should exist at any given time, when you are finished them make sure you +dispose via DisposeAsync. +Starting in version 2.x the **DevToolsContext** multiple calls to CreateDevToolsContextAsync will return the same +instance per ChromiumWebBrowser and will be Disposed when the ChromiumWebBrowser is Disposed. If you need to use +the DevToolsContext in multiple places in your code, calling CreateDevToolsContextAsync is now supported without dispoing. +If the DevToolsContext is disposed then calls to CreateDevToolsContextAsync will create a new instance. -If you have an issue or a question: +```c# +// Add using CefSharp.Dom; to get access to the +// CreateDevToolsContextAsync extension method +var devtoolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync(); -* Ask a question on [Stack Overflow](https://stackoverflow.com/search?q=puppeteer-sharp). -* File a [new issue](https://github.com/hardkoded/puppeteer-sharp/issues/new). +// Manually dispose of context (prefer DisposeAsync as the whole API is async) +await devToolsContext.DisposeAsync(); +``` -## Contributing Guide +```c# +// Dispose automatically via await using +// https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#using-async-disposable +await using var devtoolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync(); +``` -See [this document](https://github.com/hardkoded/puppeteer-sharp/blob/master/CONTRIBUTING.md) for information on how to contribute. +## DOM Access -# Usage +Read/write to the DOM + + +```cs +// Add using CefSharp.Dom to access CreateDevToolsContextAsync and related extension methods. +await using var devToolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync(); -## Take screenshots +await devToolsContext.GoToAsync("http://www.google.com"); - - -```cs -using var browserFetcher = new BrowserFetcher(); -await browserFetcher.DownloadAsync(); -await using var browser = await Puppeteer.LaunchAsync( - new LaunchOptions { Headless = true }); -await using var page = await browser.NewPageAsync(); -await page.GoToAsync("http://www.google.com"); -await page.ScreenshotAsync(outputFile); -``` -snippet source | anchor - +// Get element by Id +// https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector +var element = await devToolsContext.QuerySelectorAsync("#myElementId"); -You can also change the view port before generating the screenshot +//Strongly typed element types (this is only a subset of the types mapped) +var htmlDivElement = await devToolsContext.QuerySelectorAsync("#myDivElementId"); +var htmlSpanElement = await devToolsContext.QuerySelectorAsync("#mySpanElementId"); +var htmlSelectElement = await devToolsContext.QuerySelectorAsync("#mySelectElementId"); +var htmlInputElement = await devToolsContext.QuerySelectorAsync("#myInputElementId"); +var htmlFormElement = await devToolsContext.QuerySelectorAsync("#myFormElementId"); +var htmlAnchorElement = await devToolsContext.QuerySelectorAsync("#myAnchorElementId"); +var htmlImageElement = await devToolsContext.QuerySelectorAsync("#myImageElementId"); +var htmlTextAreaElement = await devToolsContext.QuerySelectorAsync("#myTextAreaElementId"); +var htmlButtonElement = await devToolsContext.QuerySelectorAsync("#myButtonElementId"); +var htmlParagraphElement = await devToolsContext.QuerySelectorAsync("#myParagraphElementId"); +var htmlTableElement = await devToolsContext.QuerySelectorAsync("#myTableElementId"); - - -```cs -await Page.SetViewportAsync(new ViewPortOptions +// Get a custom attribute value +var customAttribute = await element.GetAttributeAsync("data-customAttribute"); + +//Set innerText property for the element +await element.SetInnerTextAsync("Welcome!"); + +//Get innerText property for the element +var innerText = await element.GetInnerTextAsync(); + +//Get all child elements +var childElements = await element.QuerySelectorAllAsync("div"); + +//Change CSS style background colour +await element.EvaluateFunctionAsync("e => e.style.backgroundColor = 'yellow'"); + +//Type text in an input field +await element.TypeAsync("Welcome to my Website!"); + +//Click The element +await element.ClickAsync(); + +// Simple way of chaining method calls together when you don't need a handle to the HtmlElement +var htmlButtonElementInnerText = await devToolsContext.QuerySelectorAsync("#myButtonElementId") + .AndThen(x => x.GetInnerTextAsync()); + +//Event Handler +//Expose a function to javascript, functions persist across navigations +//So only need to do this once +await devToolsContext.ExposeFunctionAsync("jsAlertButtonClick", () => { - Width = 500, - Height = 500 + _ = devToolsContext.EvaluateExpressionAsync("window.alert('Hello! You invoked window.alert()');"); }); -``` -snippet source | anchor - +var jsAlertButton = await devToolsContext.QuerySelectorAsync("#jsAlertButton"); -## Generate PDF files +//Write up the click event listner to call our exposed function +_ = jsAlertButton.AddEventListenerAsync("click", "jsAlertButtonClick"); - - -```cs -using var browserFetcher = new BrowserFetcher(); -await browserFetcher.DownloadAsync(); -await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions {Headless = true}); -await using var page = await browser.NewPageAsync(); -await page.GoToAsync("http://www.google.com"); -await page.PdfAsync(outputFile); +//Get a collection of HtmlElements +var divElements = await devToolsContext.QuerySelectorAllAsync("div"); + +foreach (var div in divElements) +{ + // Get a reference to the CSSStyleDeclaration + var style = await div.GetStyleAsync(); + + //Set the border to 1px solid red + await style.SetPropertyAsync("border", "1px solid red", important: true); + + await div.SetAttributeAsync("data-customAttribute", "123"); + await div.SetInnerTextAsync("Updated Div innerText"); +} + +//Using standard array +var tableRows = await htmlTableElement.GetRowsAsync().ToArrayAsync(); + +foreach (var row in tableRows) +{ + var cells = await row.GetCellsAsync().ToArrayAsync(); + foreach (var cell in cells) + { + var newDiv = await devToolsContext.CreateHtmlElementAsync("div"); + await newDiv.SetInnerTextAsync("New Div Added!"); + await cell.AppendChildAsync(newDiv); + } +} + +//Get a reference to the HtmlCollection and use async enumerable +//Requires Net Core 3.1 or higher +var tableRowsHtmlCollection = await htmlTableElement.GetRowsAsync(); + +await foreach (var row in tableRowsHtmlCollection) +{ + var cells = await row.GetCellsAsync(); + await foreach (var cell in cells) + { + var newDiv = await devToolsContext.CreateHtmlElementAsync("div"); + await newDiv.SetInnerTextAsync("New Div Added!"); + await cell.AppendChildAsync(newDiv); + } +} ``` -snippet source | anchor +snippet source | anchor ## Inject HTML - ```cs -await using var page = await browser.NewPageAsync(); -await page.SetContentAsync("
My Receipt
"); -var result = await page.GetContentAsync(); +//Wait for Initial page load +await chromiumWebBrowser.WaitForInitialLoadAsync(); + +await using var devtoolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync(); +await devtoolsContext.SetContentAsync("
My Receipt
"); +var result = await devtoolsContext.GetContentAsync(); ``` -snippet source | anchor +snippet source | anchor ## Evaluate Javascript @@ -112,78 +186,42 @@ var result = await page.GetContentAsync(); ```cs -await using var page = await browser.NewPageAsync(); -var seven = await page.EvaluateExpressionAsync("4 + 3"); -var someObject = await page.EvaluateFunctionAsync("(value) => ({a: value})", 5); +await using var devtoolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync(); +var seven = await devtoolsContext.EvaluateExpressionAsync("4 + 3"); +var someObject = await devtoolsContext.EvaluateFunctionAsync("(value) => ({a: value})", 5); Console.WriteLine(someObject.a); ``` -snippet source | anchor +snippet source | anchor -## Wait For Selector +## Take screenshots + + ```cs -using (var page = await browser.NewPageAsync()) -{ - await page.GoToAsync("http://www.spapage.com"); - await page.WaitForSelectorAsync("div.main-content") - await page.PdfAsync(outputFile)); -} -``` +//Wait for Initial page load +await chromiumWebBrowser.WaitForInitialLoadAsync(); -## Wait For Function -```cs -using (var page = await browser.NewPageAsync()) -{ - await page.GoToAsync("http://www.spapage.com"); - var watchDog = page.WaitForFunctionAsync("()=> window.innerWidth < 100"); - await page.SetViewportAsync(new ViewPortOptions { Width = 50, Height = 50 }); - await watchDog; -} -``` +await using var devToolsContext = await chromiumWebBrowser.CreateDevToolsContextAsync(); -## Connect to a remote browser +await devToolsContext.ScreenshotAsync("file.png"); +``` +snippet source | anchor + + + ```cs -var options = new ConnectOptions() -{ - BrowserWSEndpoint = $"wss://www.externalbrowser.io?token={apikey}" -}; - -var url = "https://www.google.com/"; - -using (var browser = await PuppeteerSharp.Puppeteer.ConnectAsync(options)) +// Set Viewport +await DevToolsContext.SetViewportAsync(new ViewPortOptions { - using (var page = await browser.NewPageAsync()) - { - await page.GoToAsync(url); - await page.PdfAsync("wot.pdf"); - } -} + Width = 500, + Height = 500 +}); ``` +snippet source | anchor + -# Monthly reports - * [August 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-aug-2019) - * [July 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-jul-2019) - * [June 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-jun-2019) - * [May 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-may-2019) - * [April 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-apr-2019) - * [March 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-mar-2019) - * [February 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-feb-2019) - * [January 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-jan-2019) - -# Backers - -Support us with a monthly donation and help us continue our activities. [Become a backer](https://opencollective.com/hardkoded-projects). - - - - - - - -# Thanks - -Thanks to [JetBrains](https://www.jetbrains.com/?from=PuppeteerSharp) for a community Resharper license to use on this project. - +## Generate PDF files +Currently not supported via CefSharp.Dom, use ChromiumWebBrowser.PrintToPdfAsync instead. diff --git a/appveyor-demo.yml b/appveyor-demo.yml deleted file mode 100644 index 685fdbb72..000000000 --- a/appveyor-demo.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: 1.0.{build} -branches: - only: - - master -image: Visual Studio 2019 -configuration: Release -environment: - matrix: - - framework: net471 - - framework: netcoreapp3.1 -before_build: -- ps: >- - dotnet restore .\demos\PuppeteerSharpDemos-Local.sln -build: - project: .\demos\PuppeteerSharpDemos-Local.sln - publish_nuget: true - include_nuget_references: true - verbosity: minimal -test_script: -- cmd: >- - cd .\demos\PuppeteerSharpPdfDemo - - dotnet run -p PuppeteerSharpPdfDemo-Local.csproj -f %framework% auto-exit \ No newline at end of file diff --git a/appveyor-edge.yml b/appveyor-edge.yml deleted file mode 100644 index b6a776b2e..000000000 --- a/appveyor-edge.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: 1.0.{build} -branches: - only: - - disabled -image: Visual Studio 2019 -configuration: Release -environment: - git_access_token: - secure: FxcQ9C8a/NgcQB5dFdZts6ZWEDT4zMhA4qPQAYwWc7huMmhmTIl1sbFEIaAWQMTL - PUPPETEER_EXECUTABLE_PATH: C:\Program Files (x86)\Microsoft\Edge Dev\Application\msedge.exe -install: -- ps: >- - cd c:\projects\puppeteer-sharp-edge\appveyor\edge - - .\InstallEdge.ps1 - - cd c:\projects\puppeteer-sharp-edge - -before_build: -- ps: >- - dotnet restore .\lib\PuppeteerSharp.sln - - New-SelfSignedCertificate -Subject "localhost" -FriendlyName "Puppeteer" -CertStoreLocation "cert:\CurrentUser\My" - - Get-ChildItem -Path cert:\CurrentUSer\my | where { $_.friendlyname -eq "Puppeteer" } | Export-Certificate -FilePath C:\projects\puppeteer-sharp-edge\lib\PuppeteerSharp.TestServer\testCert.cer - -build: - project: .\lib\PuppeteerSharp.sln - publish_nuget: true - include_nuget_references: true - verbosity: minimal -test_script: -- cmd: >- - cd .\lib\PuppeteerSharp.Tests - - dotnet test -f net5.0 -s test.runsettings -cache: - - $HOME/.nuget/packages \ No newline at end of file diff --git a/appveyor-fullframework.yml b/appveyor-fullframework.yml deleted file mode 100644 index 688ee97fa..000000000 --- a/appveyor-fullframework.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: 1.0.{build} -branches: - only: - - master -image: Visual Studio 2019 -configuration: Release -clone_folder: C:\projects\puppeteer-sharp -environment: - git_access_token: - secure: FxcQ9C8a/NgcQB5dFdZts6ZWEDT4zMhA4qPQAYwWc7huMmhmTIl1sbFEIaAWQMTL - matrix: - - framework: net471 -only_commits: - files: - - lib/PuppeteerSharp.AspNetFramework/ - - appveyor-fullframework.yml -before_build: -- ps: >- - dotnet restore .\lib\PuppeteerSharp.AspNetFramework.sln - - New-SelfSignedCertificate -Subject "localhost" -FriendlyName "Puppeteer" -CertStoreLocation "cert:\CurrentUser\My" - - Get-ChildItem -Path cert:\CurrentUSer\my | where { $_.friendlyname -eq "Puppeteer" } | Export-Certificate -FilePath C:\projects\puppeteer-sharp\lib\PuppeteerSharp.TestServer\testCert.cer - -build: - project: .\lib\PuppeteerSharp.AspNetFramework.sln - publish_nuget: true - include_nuget_references: true - verbosity: minimal -test: off -artifacts: -- path: PuppeteerSharp.AspNetFramework.*.nupkg - name: PuppeteerSharp.AspNetFramework Package -cache: - - $HOME/.nuget/packages diff --git a/appveyor-linux.yml b/appveyor-linux.yml deleted file mode 100644 index 2339622f2..000000000 --- a/appveyor-linux.yml +++ /dev/null @@ -1,50 +0,0 @@ -version: 1.0.{build} -branches: - only: - - master -image: ubuntu1804 -configuration: Release -environment: - MOZ_WEBRENDER: 0 - git_access_token: - secure: FxcQ9C8a/NgcQB5dFdZts6ZWEDT4zMhA4qPQAYwWc7huMmhmTIl1sbFEIaAWQMTL - matrix: - - framework: net5.0 - PRODUCT: CHROMIUM - - framework: net5.0 - PRODUCT: FIREFOX - -before_build: -- sh: >- - sudo apt-get -y install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget xvfb - - dotnet restore lib/PuppeteerSharp.sln - - dotnet dev-certs https -ep lib/PuppeteerSharp.TestServer/testCert.cer - - sudo openssl x509 -inform der -in lib/PuppeteerSharp.TestServer/testCert.cer -out /usr/local/share/ca-certificates/testCert.crt -outform pem - - sudo update-ca-certificates - -build_script: -- sh: >- - dotnet build -f $framework ./lib/PuppeteerSharp.DevicesFetcher/PuppeteerSharp.DevicesFetcher.csproj - - dotnet build -f $framework ./lib/PuppeteerSharp.Tests.DumpIO/PuppeteerSharp.Tests.DumpIO.csproj - - dotnet build -f $framework ./lib/PuppeteerSharp.TestServer/PuppeteerSharp.TestServer.csproj - - dotnet build -f $framework ./lib/PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj - -test_script: -- sh: >- - Xvfb :1 -screen 5 1024x768x8 & - - export DISPLAY=:1.5 - - cd lib/PuppeteerSharp.Tests - - dotnet test -f $framework -s test.runsettings - -cache: - - $HOME/.nuget/packages diff --git a/appveyor.yml b/appveyor.yml index 0312b0bfe..e27f3cfea 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,46 +1,45 @@ -version: 1.0.{build} +version: 3.0.{build} branches: only: - - master -image: Visual Studio 2019 + - main +image: Visual Studio 2022 configuration: Release environment: - git_access_token: - secure: FxcQ9C8a/NgcQB5dFdZts6ZWEDT4zMhA4qPQAYwWc7huMmhmTIl1sbFEIaAWQMTL matrix: - - framework: net48 - - framework: net5.0 - - framework: net48 - ENQUEUE_ASYNC_MESSAGES: true - - framework: net5.0 - ENQUEUE_ASYNC_MESSAGES: true -install: -- ps: >- - if($env:APPVEYOR_REPO_TAG -eq 'True' -And $env:framework -eq 'netcoreapp2.0') { - choco install docfx - } + - framework: netcoreapp3.1 + before_build: - ps: >- - dotnet restore .\lib\PuppeteerSharp.sln + dotnet restore .\lib\CefSharp.Dom.sln New-SelfSignedCertificate -Subject "localhost" -FriendlyName "Puppeteer" -CertStoreLocation "cert:\CurrentUser\My" - Get-ChildItem -Path cert:\CurrentUSer\my | where { $_.friendlyname -eq "Puppeteer" } | Export-Certificate -FilePath C:\projects\puppeteer-sharp\lib\PuppeteerSharp.TestServer\testCert.cer + Get-ChildItem -Path cert:\CurrentUSer\my | where { $_.friendlyname -eq "Puppeteer" } | Export-Certificate -FilePath .\lib\PuppeteerSharp.TestServer\testCert.cer build: - project: .\lib\PuppeteerSharp.sln - publish_nuget: true - include_nuget_references: true + project: .\lib\CefSharp.Dom.sln verbosity: minimal test_script: - cmd: >- cd .\lib\PuppeteerSharp.Tests - dotnet test -f %framework% -s test.runsettings -on_success: -- ps: >- - cd c:\projects\puppeteer-sharp - - c:\projects\puppeteer-sharp\appveyor\GenerateDocs.ps1 -cache: - - $HOME/.nuget/packages + dotnet test -f %framework% -s test.runsettings --test-adapter-path:. --logger:Appveyor + +# Patch SDK Style `.csproj` file: +# https://www.appveyor.com/docs/build-configuration/#net-core-csproj-files-patching +dotnet_csproj: + patch: true + file: '**\*.csproj' + version_prefix: '{version}' + +artifacts: + # pushing all *.nupkg files in build directory recursively + - path: '**\*.nupkg' + +# Publish to myget.org feed +deploy: + provider: NuGet + server: https://www.myget.org/F/cefsharp/api/v2/package + api_key: + secure: V8du2PPvMPok3Ya701jt5v2XWQgOZf52/H5wDHXBpKvXYkIIe8sonhVUy2TmEkqt + artifact: /.*(\.|\.s)nupkg/ \ No newline at end of file diff --git a/appveyor/GenerateDocs.ps1 b/appveyor/GenerateDocs.ps1 deleted file mode 100644 index ebcacb071..000000000 --- a/appveyor/GenerateDocs.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -if($env:APPVEYOR_REPO_TAG -eq 'True' -And $env:framework -eq 'netcoreapp2.2' -And $env:ENQUEUE_ASYNC_MESSAGES -eq 'true') { - git config --global credential.helper store - Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:git_access_token):x-oauth-basic@github.com`n" - - git config --global user.email "dariokondratiuk@gmail.com" - git config --global user.name "Dario Kondratiuk" - git remote add pages https://github.com/kblok/puppeteer-sharp.git - git fetch pages - git checkout master - git subtree add --prefix docs pages/gh-pages - docfx metadata docfx_project/docfx.json - docfx build docfx_project/docfx.json -o docs - git add docs/* -f - git commit -m "Docs version $($env:APPVEYOR_REPO_TAG_NAME)" - git subtree push --prefix docs pages gh-pages -} \ No newline at end of file diff --git a/appveyor/edge/ImageHelpers/ImageHelpers.psd1 b/appveyor/edge/ImageHelpers/ImageHelpers.psd1 deleted file mode 100644 index dee37963a..000000000 --- a/appveyor/edge/ImageHelpers/ImageHelpers.psd1 +++ /dev/null @@ -1,124 +0,0 @@ -# -# Module manifest for module 'ImageHelpers' -# -# Generated by: chrispat -# -# Generated on: 11/1/2017 -# - -@{ - -# Script module or binary module file associated with this manifest. -RootModule = 'ImageHelpers.psm1' - -# Version number of this module. -ModuleVersion = '0.0.1' - -# Supported PSEditions -# CompatiblePSEditions = @() - -# ID used to uniquely identify this module -GUID = 'c9334909-16a1-48f1-a94a-c7baf1b961d9' - -# Author of this module -Author = 'chrispat' - -# Company or vendor of this module -CompanyName = 'Unknown' - -# Copyright statement for this module -Copyright = '(c) 2017 chrispat. All rights reserved.' - -# Description of the functionality provided by this module -Description = 'Helper functions for creating vsts images' - -# Minimum version of the Windows PowerShell engine required by this module -# PowerShellVersion = '' - -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - -# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = '*' - -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '*' - -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = '*' - -# DSC resources to export from this module -# DscResourcesToExport = @() - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - # Tags = @() - - # A URL to the license for this module. - # LicenseUri = '' - - # A URL to the main website for this project. - # ProjectUri = '' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - # ReleaseNotes = '' - - } # End of PSData hashtable - -} # End of PrivateData hashtable - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - -} - - diff --git a/appveyor/edge/ImageHelpers/ImageHelpers.psm1 b/appveyor/edge/ImageHelpers/ImageHelpers.psm1 deleted file mode 100644 index 12df84e7a..000000000 --- a/appveyor/edge/ImageHelpers/ImageHelpers.psm1 +++ /dev/null @@ -1,19 +0,0 @@ -[CmdletBinding()] -param() - -. $PSScriptRoot\PathHelpers.ps1 -. $PSScriptRoot\InstallHelpers.ps1 -. $PSScriptRoot\MarkdownHelpers.ps1 - -Export-ModuleMember -Function @( - 'Test-MachinePath' - 'Get-MachinePath' - 'Set-MachinePath' - 'Add-MachinePathItem' - 'Get-SystemVariable' - 'Set-SystemVariable' - 'Install-MSI' - 'Install-EXE' - 'Add-ContentToMarkdown' - 'Add-SoftwareDetailsToMarkdown' -) diff --git a/appveyor/edge/ImageHelpers/InstallHelpers.ps1 b/appveyor/edge/ImageHelpers/InstallHelpers.ps1 deleted file mode 100644 index 12bb373fc..000000000 --- a/appveyor/edge/ImageHelpers/InstallHelpers.ps1 +++ /dev/null @@ -1,83 +0,0 @@ -function Install-MSI -{ - Param - ( - [String]$MsiUrl, - [String]$MsiName - ) - - $exitCode = -1 - - try - { - Write-Host "Downloading $MsiName..." - $FilePath = "${env:Temp}\$MsiName" - - Invoke-WebRequest -Uri $MsiUrl -OutFile $FilePath - - $Arguments = ('/i', $FilePath, '/QN', '/norestart' ) - - Write-Host "Starting Install $MsiName..." - $process = Start-Process -FilePath msiexec.exe -ArgumentList $Arguments -Wait -PassThru - $exitCode = $process.ExitCode - - if ($exitCode -eq 0 -or $exitCode -eq 3010) - { - Write-Host -Object 'Installation successful' - return $exitCode - } - else - { - Write-Host -Object "Non zero exit code returned by the installation process : $exitCode." - exit $exitCode - } - } - catch - { - Write-Host -Object "Failed to install the MSI $MsiName" - Write-Host -Object $_.Exception.Message - exit -1 - } -} - - -function Install-EXE -{ - Param - ( - [String]$Url, - [String]$Name, - [String[]]$ArgumentList - ) - - $exitCode = -1 - - try - { - Write-Host "Downloading $Name..." - $FilePath = "${env:Temp}\$Name" - - Invoke-WebRequest -Uri $Url -OutFile $FilePath - - Write-Host "Starting Install $Name..." - $process = Start-Process -FilePath $FilePath -ArgumentList $ArgumentList -Wait -PassThru - $exitCode = $process.ExitCode - - if ($exitCode -eq 0 -or $exitCode -eq 3010) - { - Write-Host -Object 'Installation successful' - return $exitCode - } - else - { - Write-Host -Object "Non zero exit code returned by the installation process : $exitCode." - return $exitCode - } - } - catch - { - Write-Host -Object "Failed to install the Executable $Name" - Write-Host -Object $_.Exception.Message - return -1 - } -} diff --git a/appveyor/edge/ImageHelpers/MarkdownHelpers.ps1 b/appveyor/edge/ImageHelpers/MarkdownHelpers.ps1 deleted file mode 100644 index da5d53e2c..000000000 --- a/appveyor/edge/ImageHelpers/MarkdownHelpers.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -function Add-ContentToMarkdown { - [CmdletBinding()] - param( - $Content = "" - ) - - Add-Content 'C:\InstalledSoftware.md' $Content -} - - -function Add-SoftwareDetailsToMarkdown { - [CmdletBinding()] - param( - $SoftwareName = "", - $DescriptionMarkdown = "" - ) - -$Content = @" - -## $SoftwareName - -$DescriptionMarkdown -"@ - Add-ContentToMarkdown -Content $Content -} diff --git a/appveyor/edge/ImageHelpers/PathHelpers.ps1 b/appveyor/edge/ImageHelpers/PathHelpers.ps1 deleted file mode 100644 index a1d818d6c..000000000 --- a/appveyor/edge/ImageHelpers/PathHelpers.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -function Test-MachinePath{ - [CmdletBinding()] - param( - [string]$PathItem - ) - - $currentPath = Get-MachinePath - - $pathItems = $currentPath.Split(';') - - if($pathItems.Contains($PathItem)) - { - return $true - } - else - { - return $false - } -} - -function Set-MachinePath{ - [CmdletBinding()] - param( - [string]$NewPath - ) - Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path -Value $NewPath - return $NewPath -} - -function Add-MachinePathItem -{ - [CmdletBinding()] - param( - [string]$PathItem - ) - - $currentPath = Get-MachinePath - $newPath = $PathItem + ';' + $currentPath - return Set-MachinePath -NewPath $newPath -} - -function Get-MachinePath{ - [CmdletBinding()] - param( - - ) - $currentPath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path - return $currentPath -} - -function Get-SystemVariable{ - [CmdletBinding()] - param( - [string]$SystemVariable - ) - $currentPath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name $SystemVariable).$SystemVariable - return $currentPath -} - -function Set-SystemVariable{ - [CmdletBinding()] - param( - [string]$SystemVariable, - [string]$Value - ) - Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name $SystemVariable -Value $Value - return $Value -} \ No newline at end of file diff --git a/appveyor/edge/InstallEdge.ps1 b/appveyor/edge/InstallEdge.ps1 deleted file mode 100644 index 32f01a376..000000000 --- a/appveyor/edge/InstallEdge.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -################################################################################ -## File: Install-EdgePreview.ps1 -## Team: Automated Testing -## Desc: Install Microsoft Edge preview -################################################################################ -#Save the current value in the $p variable. -Copy-Item -Path .\ImageHelpers -Destination $home\Documents\WindowsPowerShell\Modules\ImageHelpers -Recurse -Force - -Import-Module -Name ImageHelpers -Force - -$temp_install_dir = 'C:\Windows\Installer' -New-Item -Path $temp_install_dir -ItemType Directory -Force - -Install-EXE -Url "https://go.microsoft.com/fwlink/?linkid=2069324&Channel=Dev&language=en-us&Consent=1" -Name "MicrosoftEdgeSetup.exe" -ArgumentList "/silent /install" - -# Add some things to stop Edge from auto updating. -New-NetFirewallRule -DisplayName "BlockEdgeUpdate" -Direction Outbound -Action Block -Program "C:\Program Files (x86)\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe" - -New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\MicrosoftEdgeUpdate" -Force -New-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\MicrosoftEdgeUpdate" -Name "AutoUpdateCheckPeriodMinutes" -Value 00000000 -Force -New-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\MicrosoftEdgeUpdate" -Name "UpdateDefault" -Value 00000000 -Force -New-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\MicrosoftEdgeUpdate" -Name "DisableAutoUpdateChecksCheckboxValue" -Value 00000001 -Force -New-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\MicrosoftEdgeUpdate" -Name "Update{8A69D345-D564-463C-AFF1-A69D9E530F96}" -Value 00000000 -Force diff --git a/demos/PuppeteerSharpDemos-Local.sln b/demos/PuppeteerSharpDemos-Local.sln deleted file mode 100644 index a18b21626..000000000 --- a/demos/PuppeteerSharpDemos-Local.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2036 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PuppeteerSharpPdfDemo-Local", "PuppeteerSharpPdfDemo\PuppeteerSharpPdfDemo-Local.csproj", "{1B8CE751-9762-45DD-8AEC-A6C1D74A7730}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PuppeteerSharp", "..\lib\PuppeteerSharp\PuppeteerSharp.csproj", "{4BA6F28A-EFF2-4B43-9B5A-86333DC64C64}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Release|Any CPU.Build.0 = Release|Any CPU - {4BA6F28A-EFF2-4B43-9B5A-86333DC64C64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BA6F28A-EFF2-4B43-9B5A-86333DC64C64}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BA6F28A-EFF2-4B43-9B5A-86333DC64C64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BA6F28A-EFF2-4B43-9B5A-86333DC64C64}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7C1E8495-E1E4-426D-B28C-E0C0C5824B9C} - EndGlobalSection -EndGlobal diff --git a/demos/PuppeteerSharpPdfDemo/Program.cs b/demos/PuppeteerSharpPdfDemo/Program.cs deleted file mode 100644 index 030e840b3..000000000 --- a/demos/PuppeteerSharpPdfDemo/Program.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; -using System.IO; -using System.Threading.Tasks; -using PuppeteerSharp; - -namespace PuppeteerSharpPdfDemo -{ - class MainClass - { - public static async Task Main(string[] args) - { - var options = new LaunchOptions - { - Headless = true - }; - - Console.WriteLine("Downloading chromium"); - using var browserFetcher = new BrowserFetcher(); - await browserFetcher.DownloadAsync(); - - Console.WriteLine("Navigating google"); - using (var browser = await Puppeteer.LaunchAsync(options)) - using (var page = await browser.NewPageAsync()) - { - await page.GoToAsync("http://www.google.com"); - - Console.WriteLine("Generating PDF"); - await page.PdfAsync(Path.Combine(Directory.GetCurrentDirectory(), "google.pdf")); - - Console.WriteLine("Export completed"); - - if (!args.Any(arg => arg == "auto-exit")) - { - Console.ReadLine(); - } - } - } - } -} diff --git a/demos/PuppeteerSharpPdfDemo/PuppeteerSharpPdfDemo-Local.csproj b/demos/PuppeteerSharpPdfDemo/PuppeteerSharpPdfDemo-Local.csproj deleted file mode 100644 index 002d82918..000000000 --- a/demos/PuppeteerSharpPdfDemo/PuppeteerSharpPdfDemo-Local.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - netcoreapp3.1;net471 - 8.0 - - - win7-x64 - - - - - diff --git a/docfx_project/api/index.md b/docfx_project/api/index.md index 52c957927..4a3c32277 100644 --- a/docfx_project/api/index.md +++ b/docfx_project/api/index.md @@ -1,6 +1,9 @@ -# Puppeteer Sharp +# PuppeteerSharp Embedded (CefSharp) -Puppeteer Sharp is a .NET port of the official [Node.JS Puppeteer API](https://github.com/GoogleChrome/puppeteer). +Puppeteer Embedded is a fork of [puppeteer-sharp by Darío Kondratiuk](https://github.com/hardkoded/puppeteer-sharp) that has been adapted specifically for use with CefSharp. +Direct communication with the ChromiumWebBrowser instance rather than opening a web socket. +1:1 mapping of Page and ChromiumWebBrowser +CEF only supports a subset of features, features will be added/removed as the project matures # Usage diff --git a/docfx_project/index.md b/docfx_project/index.md index 0e1ffeafa..3a5a0a4ee 100644 --- a/docfx_project/index.md +++ b/docfx_project/index.md @@ -1,38 +1,21 @@ -# Puppeteer Sharp +# PuppeteerSharp Embedded (CefSharp) -Puppeteer Sharp is a .NET port of the official [Node.JS Puppeteer API](https://github.com/GoogleChrome/puppeteer). +Puppeteer Embedded is a fork of [puppeteer-sharp by Darío Kondratiuk](https://github.com/hardkoded/puppeteer-sharp) that has been adapted specifically for use with CefSharp. +Direct communication with the ChromiumWebBrowser instance rather than opening a web socket. +1:1 mapping of Page and ChromiumWebBrowser +CEF only supports a subset of features, features will be added/removed as the project matures +# Prerequisites -# Puppeteer-Sharp 3 is here! - -Check out the [blog post](https://www.hardkoded.com/blog/puppeteer-sharp-3-is-here)! - -# Sponsor the project + * .Net 4.7.2 or .Net Core 3.1 or greater + * CefSharp 95.7.141 or greater -If you are making money using Puppeteer-Sharp, consider sponsoring this project. -This will give you priority support and code reviews (depending on the tier), and it will help this **community based** project keep moving. -[Click here](https://github.com/sponsors/hardkoded) to sponsor this project. +# Questions and Support -# Prerequisites +If you have an issue or a question: - * As Puppeteer-Sharp is a NetStandard 2.0 library, The minimum platform versions are .NET Framework 4.6.1 and .NET Core 2.0. [Read more](https://docs.microsoft.com/en-us/dotnet/standard/net-standard). - * The minimum Windows versions supporting the WebSocket library are Windows 8 and Windows Server 2012. [Read more](https://docs.microsoft.com/en-us/dotnet/api/system.net.websockets?redirectedfrom=MSDN&view=netframework-4.7.2). - * Mono is required on **Linux**. Read more about installing Mono [here](https://www.mono-project.com/download/stable/#download-lin-ubuntu). - * If you have issues running Chrome on Linux, the Puppeteer repo has a [great troubleshooting guide](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md). - -# Monthly reports - * [August 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-aug-2019) - * [July 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-jul-2019) - * [June 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-jun-2019) - * [May 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-may-2019) - * [April 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-apr-2019) - * [March 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-mar-2019) - * [February 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-feb-2019) - * [January 2019](http://www.hardkoded.com/blog/puppeteer-sharp-monthly-jan-2019) +* Ask a question on [Discussions](https://github.com/cefsharp/PuppeteerSharp.Embedded/discussions). # Useful links -* [GitHub-Repo](https://github.com/hardkoded/puppeteer-sharp) -* Slack channel [#puppeteer-sharp](https://www.hardkoded.com/goto/pptr-slack) -* [StackOverflow](https://stackoverflow.com/search?q=puppeteer-sharp) -* [Issues](https://github.com/kblok/puppeteer-sharp/issues?utf8=%E2%9C%93&q=is%3Aissue) +* [Puppeteer-Sharp GitHub-Repo](https://github.com/hardkoded/puppeteer-sharp) diff --git a/demos/PuppeteerSharpDemos.sln b/lib/CefSharp.Dom.OutOfProcess.sln similarity index 51% rename from demos/PuppeteerSharpDemos.sln rename to lib/CefSharp.Dom.OutOfProcess.sln index 167465c79..58518e8cd 100644 --- a/demos/PuppeteerSharpDemos.sln +++ b/lib/CefSharp.Dom.OutOfProcess.sln @@ -1,9 +1,9 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2036 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32510.428 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PuppeteerSharpPdfDemo", "PuppeteerSharpPdfDemo\PuppeteerSharpPdfDemo.csproj", "{1B8CE751-9762-45DD-8AEC-A6C1D74A7730}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CefSharp.Dom.OutOfProcess", "PuppeteerSharp\CefSharp.Dom.OutOfProcess.csproj", "{DDAA533B-BAD2-4EC0-8F91-DED3C7C9F1B1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,15 +11,15 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B8CE751-9762-45DD-8AEC-A6C1D74A7730}.Release|Any CPU.Build.0 = Release|Any CPU + {DDAA533B-BAD2-4EC0-8F91-DED3C7C9F1B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDAA533B-BAD2-4EC0-8F91-DED3C7C9F1B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDAA533B-BAD2-4EC0-8F91-DED3C7C9F1B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDAA533B-BAD2-4EC0-8F91-DED3C7C9F1B1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7C1E8495-E1E4-426D-B28C-E0C0C5824B9C} + SolutionGuid = {BE091CAC-CDFC-4EE8-87E7-0EE85DC98FBA} EndGlobalSection EndGlobal diff --git a/lib/CefSharp.Dom.WinForms.Example/BrowserForm.Designer.cs b/lib/CefSharp.Dom.WinForms.Example/BrowserForm.Designer.cs new file mode 100644 index 000000000..fddca34b1 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/BrowserForm.Designer.cs @@ -0,0 +1,253 @@ +namespace CefSharp.Dom.WinForms.Example +{ + partial class BrowserForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BrowserForm)); + this.toolStripContainer = new System.Windows.Forms.ToolStripContainer(); + this.statusLabel = new System.Windows.Forms.Label(); + this.outputLabel = new System.Windows.Forms.Label(); + this.toolStrip1 = new System.Windows.Forms.ToolStrip(); + this.backButton = new System.Windows.Forms.ToolStripButton(); + this.forwardButton = new System.Windows.Forms.ToolStripButton(); + this.urlTextBox = new System.Windows.Forms.ToolStripTextBox(); + this.goButton = new System.Windows.Forms.ToolStripButton(); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showDevToolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.devToolsExampleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.highlightLinksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.waitForSelectorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripContainer.ContentPanel.SuspendLayout(); + this.toolStripContainer.TopToolStripPanel.SuspendLayout(); + this.toolStripContainer.SuspendLayout(); + this.toolStrip1.SuspendLayout(); + this.menuStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // toolStripContainer + // + // + // toolStripContainer.ContentPanel + // + this.toolStripContainer.ContentPanel.Controls.Add(this.statusLabel); + this.toolStripContainer.ContentPanel.Controls.Add(this.outputLabel); + this.toolStripContainer.ContentPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.toolStripContainer.ContentPanel.Size = new System.Drawing.Size(852, 516); + this.toolStripContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.toolStripContainer.LeftToolStripPanelVisible = false; + this.toolStripContainer.Location = new System.Drawing.Point(0, 24); + this.toolStripContainer.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.toolStripContainer.Name = "toolStripContainer"; + this.toolStripContainer.RightToolStripPanelVisible = false; + this.toolStripContainer.Size = new System.Drawing.Size(852, 541); + this.toolStripContainer.TabIndex = 0; + this.toolStripContainer.Text = "toolStripContainer1"; + // + // toolStripContainer.TopToolStripPanel + // + this.toolStripContainer.TopToolStripPanel.Controls.Add(this.toolStrip1); + // + // statusLabel + // + this.statusLabel.AutoSize = true; + this.statusLabel.Dock = System.Windows.Forms.DockStyle.Bottom; + this.statusLabel.Location = new System.Drawing.Point(0, 486); + this.statusLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.statusLabel.Name = "statusLabel"; + this.statusLabel.Size = new System.Drawing.Size(0, 15); + this.statusLabel.TabIndex = 1; + // + // outputLabel + // + this.outputLabel.AutoSize = true; + this.outputLabel.Dock = System.Windows.Forms.DockStyle.Bottom; + this.outputLabel.Location = new System.Drawing.Point(0, 501); + this.outputLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.outputLabel.Name = "outputLabel"; + this.outputLabel.Size = new System.Drawing.Size(0, 15); + this.outputLabel.TabIndex = 0; + // + // toolStrip1 + // + this.toolStrip1.Dock = System.Windows.Forms.DockStyle.None; + this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.backButton, + this.forwardButton, + this.urlTextBox, + this.goButton}); + this.toolStrip1.Location = new System.Drawing.Point(0, 0); + this.toolStrip1.Name = "toolStrip1"; + this.toolStrip1.Padding = new System.Windows.Forms.Padding(0); + this.toolStrip1.Size = new System.Drawing.Size(852, 25); + this.toolStrip1.Stretch = true; + this.toolStrip1.TabIndex = 0; + this.toolStrip1.Layout += new System.Windows.Forms.LayoutEventHandler(this.HandleToolStripLayout); + // + // backButton + // + this.backButton.Enabled = false; + this.backButton.Image = global::CefSharp.Dom.WinForms.Example.Properties.Resources.nav_left_green; + this.backButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.backButton.Name = "backButton"; + this.backButton.Size = new System.Drawing.Size(52, 22); + this.backButton.Text = "Back"; + this.backButton.Click += new System.EventHandler(this.BackButtonClick); + // + // forwardButton + // + this.forwardButton.Enabled = false; + this.forwardButton.Image = global::CefSharp.Dom.WinForms.Example.Properties.Resources.nav_right_green; + this.forwardButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.forwardButton.Name = "forwardButton"; + this.forwardButton.Size = new System.Drawing.Size(70, 22); + this.forwardButton.Text = "Forward"; + this.forwardButton.Click += new System.EventHandler(this.ForwardButtonClick); + // + // urlTextBox + // + this.urlTextBox.AutoSize = false; + this.urlTextBox.Name = "urlTextBox"; + this.urlTextBox.Size = new System.Drawing.Size(500, 25); + this.urlTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UrlTextBoxKeyUp); + // + // goButton + // + this.goButton.Image = global::CefSharp.Dom.WinForms.Example.Properties.Resources.nav_plain_green; + this.goButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.goButton.Name = "goButton"; + this.goButton.Size = new System.Drawing.Size(42, 22); + this.goButton.Text = "Go"; + this.goButton.Click += new System.EventHandler(this.GoButtonClick); + // + // menuStrip1 + // + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem, + this.devToolsExampleToolStripMenuItem}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); + this.menuStrip1.Size = new System.Drawing.Size(852, 24); + this.menuStrip1.TabIndex = 1; + this.menuStrip1.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.showDevToolsToolStripMenuItem, + this.exitToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); + this.fileToolStripMenuItem.Text = "File"; + // + // showDevToolsToolStripMenuItem + // + this.showDevToolsToolStripMenuItem.Name = "showDevToolsToolStripMenuItem"; + this.showDevToolsToolStripMenuItem.Size = new System.Drawing.Size(153, 22); + this.showDevToolsToolStripMenuItem.Text = "Show DevTools"; + this.showDevToolsToolStripMenuItem.Click += new System.EventHandler(this.ShowDevToolsMenuItemClick); + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.Size = new System.Drawing.Size(153, 22); + this.exitToolStripMenuItem.Text = "Exit"; + this.exitToolStripMenuItem.Click += new System.EventHandler(this.ExitMenuItemClick); + // + // devToolsExampleToolStripMenuItem + // + this.devToolsExampleToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.highlightLinksToolStripMenuItem, + this.waitForSelectorToolStripMenuItem}); + this.devToolsExampleToolStripMenuItem.Name = "devToolsExampleToolStripMenuItem"; + this.devToolsExampleToolStripMenuItem.Size = new System.Drawing.Size(114, 20); + this.devToolsExampleToolStripMenuItem.Text = "DevTools Example"; + // + // highlightLinksToolStripMenuItem + // + this.highlightLinksToolStripMenuItem.Name = "highlightLinksToolStripMenuItem"; + this.highlightLinksToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.highlightLinksToolStripMenuItem.Text = "Highlight Links"; + this.highlightLinksToolStripMenuItem.Click += new System.EventHandler(this.HighlightLinksToolStripMenuItemClick); + // + // waitForSelectorToolStripMenuItem + // + this.waitForSelectorToolStripMenuItem.Name = "waitForSelectorToolStripMenuItem"; + this.waitForSelectorToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.waitForSelectorToolStripMenuItem.Text = "Wait for Selector"; + this.waitForSelectorToolStripMenuItem.Click += new System.EventHandler(this.OnWaitForSelectorToolStripMenuItemClick); + // + // BrowserForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(852, 565); + this.Controls.Add(this.toolStripContainer); + this.Controls.Add(this.menuStrip1); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MainMenuStrip = this.menuStrip1; + this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.Name = "BrowserForm"; + this.Text = "BrowserForm"; + this.toolStripContainer.ContentPanel.ResumeLayout(false); + this.toolStripContainer.ContentPanel.PerformLayout(); + this.toolStripContainer.TopToolStripPanel.ResumeLayout(false); + this.toolStripContainer.TopToolStripPanel.PerformLayout(); + this.toolStripContainer.ResumeLayout(false); + this.toolStripContainer.PerformLayout(); + this.toolStrip1.ResumeLayout(false); + this.toolStrip1.PerformLayout(); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ToolStripContainer toolStripContainer; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.ToolStripButton backButton; + private System.Windows.Forms.ToolStripButton forwardButton; + private System.Windows.Forms.ToolStripTextBox urlTextBox; + private System.Windows.Forms.ToolStripButton goButton; + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.Label outputLabel; + private System.Windows.Forms.Label statusLabel; + private System.Windows.Forms.ToolStripMenuItem showDevToolsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem devToolsExampleToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem highlightLinksToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem waitForSelectorToolStripMenuItem; + } +} diff --git a/lib/CefSharp.Dom.WinForms.Example/BrowserForm.cs b/lib/CefSharp.Dom.WinForms.Example/BrowserForm.cs new file mode 100644 index 000000000..239d7d53a --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/BrowserForm.cs @@ -0,0 +1,240 @@ +// Copyright © 2022 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using CefSharp.Dom.WinForms.Example.Controls; +using CefSharp.WinForms; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace CefSharp.Dom.WinForms.Example +{ + public partial class BrowserForm : Form + { +#if DEBUG + private const string Build = "Debug"; +#else + private const string Build = "Release"; +#endif + private readonly string title = "CefSharp.Dom.WinForms.Example (" + Build + ")"; + private readonly ChromiumWebBrowser browser; + + public BrowserForm() + { + InitializeComponent(); + + Text = title; + WindowState = FormWindowState.Maximized; + + browser = new ChromiumWebBrowser("www.google.com"); + toolStripContainer.ContentPanel.Controls.Add(browser); + + browser.IsBrowserInitializedChanged += OnIsBrowserInitializedChanged; + browser.LoadingStateChanged += OnLoadingStateChanged; + browser.ConsoleMessage += OnBrowserConsoleMessage; + browser.StatusMessage += OnBrowserStatusMessage; + browser.TitleChanged += OnBrowserTitleChanged; + browser.AddressChanged += OnBrowserAddressChanged; + browser.LoadError += OnBrowserLoadError; + + var version = string.Format("Chromium: {0}, CEF: {1}, CefSharp: {2}", + Cef.ChromiumVersion, Cef.CefVersion, Cef.CefSharpVersion); + +#if NETCOREAPP + // .NET Core + var environment = string.Format("Environment: {0}, Runtime: {1}", + System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(), + System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription); +#else + // .NET Framework + var bitness = Environment.Is64BitProcess ? "x64" : "x86"; + var environment = String.Format("Environment: {0}", bitness); +#endif + + DisplayOutput(string.Format("{0}, {1}", version, environment)); + } + + private void OnBrowserLoadError(object sender, LoadErrorEventArgs e) + { + //Actions that trigger a download will raise an aborted error. + //Aborted is generally safe to ignore + if (e.ErrorCode == CefErrorCode.Aborted) + { + return; + } + + var errorHtml = string.Format("

Failed to load URL {0} with error {1} ({2}).

", + e.FailedUrl, e.ErrorText, e.ErrorCode); + + _ = e.Browser.SetMainFrameDocumentContentAsync(errorHtml); + + //AddressChanged isn't called for failed Urls so we need to manually update the Url TextBox + this.InvokeOnUiThreadIfRequired(() => urlTextBox.Text = e.FailedUrl); + } + + private void OnIsBrowserInitializedChanged(object sender, EventArgs e) + { + var b = ((ChromiumWebBrowser)sender); + + this.InvokeOnUiThreadIfRequired(() => b.Focus()); + } + + private void OnBrowserConsoleMessage(object sender, ConsoleMessageEventArgs args) + { + DisplayOutput(string.Format("Line: {0}, Source: {1}, Message: {2}", args.Line, args.Source, args.Message)); + } + + private void OnBrowserStatusMessage(object sender, StatusMessageEventArgs args) + { + this.InvokeOnUiThreadIfRequired(() => statusLabel.Text = args.Value); + } + + private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs args) + { + SetCanGoBack(args.CanGoBack); + SetCanGoForward(args.CanGoForward); + + this.InvokeOnUiThreadIfRequired(() => SetIsLoading(!args.CanReload)); + } + + private void OnBrowserTitleChanged(object sender, TitleChangedEventArgs args) + { + this.InvokeOnUiThreadIfRequired(() => Text = title + " - " + args.Title); + } + + private void OnBrowserAddressChanged(object sender, AddressChangedEventArgs args) + { + this.InvokeOnUiThreadIfRequired(() => urlTextBox.Text = args.Address); + } + + private void SetCanGoBack(bool canGoBack) + { + this.InvokeOnUiThreadIfRequired(() => backButton.Enabled = canGoBack); + } + + private void SetCanGoForward(bool canGoForward) + { + this.InvokeOnUiThreadIfRequired(() => forwardButton.Enabled = canGoForward); + } + + private void SetIsLoading(bool isLoading) + { + goButton.Text = isLoading ? + "Stop" : + "Go"; + goButton.Image = isLoading ? + Properties.Resources.nav_plain_red : + Properties.Resources.nav_plain_green; + + HandleToolStripLayout(); + } + + public void DisplayOutput(string output) + { + this.InvokeOnUiThreadIfRequired(() => outputLabel.Text = output); + } + + private void HandleToolStripLayout(object sender, LayoutEventArgs e) + { + HandleToolStripLayout(); + } + + private void HandleToolStripLayout() + { + var width = toolStrip1.Width; + foreach (ToolStripItem item in toolStrip1.Items) + { + if (item != urlTextBox) + { + width -= item.Width - item.Margin.Horizontal; + } + } + urlTextBox.Width = Math.Max(0, width - urlTextBox.Margin.Horizontal - 18); + } + + private void ExitMenuItemClick(object sender, EventArgs e) + { + browser.Dispose(); + Cef.Shutdown(); + Close(); + } + + private void GoButtonClick(object sender, EventArgs e) + { + LoadUrl(urlTextBox.Text); + } + + private void BackButtonClick(object sender, EventArgs e) + { + browser.Back(); + } + + private void ForwardButtonClick(object sender, EventArgs e) + { + browser.Forward(); + } + + private void UrlTextBoxKeyUp(object sender, KeyEventArgs e) + { + if (e.KeyCode != Keys.Enter) + { + return; + } + + LoadUrl(urlTextBox.Text); + } + + private void LoadUrl(string url) + { + if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) + { + browser.Load(url); + } + } + + private void ShowDevToolsMenuItemClick(object sender, EventArgs e) + { + browser.ShowDevTools(); + } + + private async void HighlightLinksToolStripMenuItemClick(object sender, EventArgs e) + { + var devToolsContext = await browser.CreateDevToolsContextAsync(); + + var links = await devToolsContext.QuerySelectorAllAsync("a"); + + var previousColor = await links[0].GetStyleAsync().AndThen(x => x.GetPropertyValueAsync("background-color")); + + var color = previousColor == "yellow" ? "red" : "yellow"; + + foreach (var link in links) + { + await link.GetStyleAsync().AndThen(x => x.SetPropertyAsync("background-color", color)); + } + + await devToolsContext.DisposeAsync(); + } + + private async void OnWaitForSelectorToolStripMenuItemClick(object sender, EventArgs e) + { + _ = await browser.LoadUrlAsync("https://developers.google.com/web/"); + + var devToolsContext = await browser.CreateDevToolsContextAsync(); + + var inputSelector = "input[name='q']"; + var waitForSelectorOptions = new WaitForSelectorOptions { Timeout = 30000 }; + var emailInput = await devToolsContext.WaitForSelectorAsync(inputSelector, waitForSelectorOptions); + await emailInput.TypeAsync("Headless"); + + // Artifically sleep a little for demo purposes + await Task.Delay(5000); + + var emailInput1 = await devToolsContext.WaitForSelectorAsync(inputSelector, waitForSelectorOptions); + await emailInput1.TypeAsync(" Chrome"); + + await devToolsContext.DisposeAsync(); + } + } +} diff --git a/lib/CefSharp.Dom.WinForms.Example/BrowserForm.resx b/lib/CefSharp.Dom.WinForms.Example/BrowserForm.resx new file mode 100644 index 000000000..bcb9f497e --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/BrowserForm.resx @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 122, 17 + + + + + AAABAAcAMDAAAAEACACoDgAAdgAAACAgAAABAAgAqAgAAB4PAAAQEAAAAQAIAGgFAADGFwAAMDAAAAEA + IACoJQAALh0AACAgAAABACAAqBAAANZCAAAYGAAAAQAgAIgJAAB+UwAAEBAAAAEAIABoBAAABl0AACgA + AAAwAAAAYAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAAAADqlE0Ad3Z+ADg3 + OwA2NTkAPj1BAF5dYQC6ub0ANDM2AFdWWQChn6MAa2psAJwc/wCdI/kAnSLxAKQ07ACpTeYAqWrQALaH + 0QDBndQAmivPAK2QuwBlY2YApDzQALti4wCyWNAAwW3PAKRLsQC2rLcAlzmbAEtJSwBzcXMAnUaZALtx + tgCkV5oAynq9AJQ8fACLUHMAzIyyAKhnjADVucEAOjY3AJGNjgDRlaIAqqOkAM6hpACaWVkAxcLCAOip + oQCEf34AUUZDANGqngC2s7IAWzIfAFY5LABCOzgAZDYeAGQ8KACndFoAqnpjALWajABhVU8AxL67ANbU + 0wCLTSoAj1AsAI1OKwCVVC8AkFMxAJJXNgCVWjgAlVs6AJdePgCeaUsAoG1QAKJvUwCVZk0Ai2JLAK6A + ZgDLtKcA0LuwAIxOKgCcWzMAnGVCAH5bRQCyiXAAwJZ8AJ6AbgC+nosAxaaUANjGuwDOycYAWUg9AKOJ + dwCNeGoA0busANXDtwDX0c0AbU43ALOIZwCZd14Ah2lTAE1ANgDHrpwAv7GnANDIwgDp4t0Avbm2AO7q + 5wDplE0A6JNNAOKPSwDqlE4A55NNAOaTTgDkkk4A65dRAOGRTgDekE8A2o5PAOydWgDVjVEAuH5QANWU + XgDtpWkAsXxPAKl2TADenGUA5KJrAO2sdgDvsX4A26Z6AOuzhADhrYIA7L2VAM+nhQD0yqcArJB5APTQ + sgD10rUA38GpAPLWvwDdx7UA89vIAO3WwwDl2c8A2s/GANnOxQDYzcQA18zDAMzDvADKwboAyL+4ANbN + xgCxq6YAycO+AMfBvAC9t7IA1M7JANLMxwDLxcAA493YAO3n4gDAu7cA6JRGAOmWSgDpl0wA6phPAM+H + SADrmlQA4phVAOyiYQDOjVUA7atuAMqWaQDwuYYAyp95APHAlABwWUUA0q6OANSylAB+cGMA48y4AN/U + ygDe08kA3dLIANzRxwDb0MYA2s/FAOLXzQDTycAA0si/AM3EvADMw7sAycC4AOXc1ADa0ckAxb22AMK6 + swDe1s8A3NTNANfPyADRysQA5ZRBAOuoZADMlF8A05xoAOHWywDVy8EAz8a9AN3UywDp4NcAysS+ALm1 + sQC1sa0A8+7pAO3p5QDPzMkA9vTyAOKQNwDfnlgA77Z7AI5yVADyx5gAeWlXAKp0MQB7WjEAuJVsANyN + KADank0Am2kgALqCMQDFizUAuIpKAJd2SABaSC0A0IUSALJ2FQDBiCoA1ZYyAIVfJQDXmz8A4axWAIlq + OgCog0oAimw9AOOzaQDy06MAfX2GAFFRVQDOztQARUVHANvb4AD///8AAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC+1tVpmZmpsyaChzF1dAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAC2ibKJ4LTEbMnKysbLoaBrop2dAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAtomysli0LKPU1NTGxsubY8uhzKLbQG0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADWJsrCJZV42 + xby8vZSVlpeiy8xra2vbnUAxHgAAAAAAAAAAAAAAAAAAAAAAAAAAtbKwsIVlXgnFu7u8vLy9lJWWmKJd + ol3Wop0xpdcAAAAAAAAAAAAAAAAAAAAAAACJss+ufeVeCcW5ubq6u7y8vL2+lpfS02uZx23bQKXXAAAA + AAAAAAAAAAAAAAAAALKwz66uiV4sbL+/0dG5ubq7vLy9lJWX0sHTmW3InkCl1wAAAAAAAAAAAAAAAAAA + srDPrnt75CAM1ZO/v7/R0bm6u7y8vZSVl9LBwsSf18hAbdcAAAAAAAAAAAAAAACysNB9e3t45AssbmyT + k5O/v9HRubq7vLyUlZfSwcKZyNefpW3YAAAAAAAAAAAAAFiw0H17eXh3quIEDUHak5OTk7+/0bm6u7y8 + vZWX0sHCxMjY16XX2AAAAAAAAAAAALDQ0Ht5eHd1daq3IQs22cWTk5O/v9G5uru8vJSVl9LBw5rI2Nht + 1wAAAAAAAAAAz8+Fe3l4d3V0c3CqYAT7LNnVk5OTv7/Rubq7vLyUlZfA05nHnzbYn9gAAAAAAAAAroWB + eXh3dXRzcG9vcWb6CA2jbJOTk5PR0bm6u7y9vpbSwcKZx9fY2NceAAAAAACF0IV5eHd1c3NwbwMDAwN8 + MyELXdmkpKRsxdG5uru8vb6X0sHDmsg22NfYAAAAAADQh4F4d3VzcG9vAwMDA29yhSwE+wzcbm5ubtrV + 0bq8vL2Vl8DTmcef2Jw2AAAAAC19hnh3dXRzb28DAwMDb62Kj5Kc+ggzDG3bbtpupJO7vL2UltLBwprI + 19ic2AAAAIeBh3d1dHNwbwMDAwNvg42PuF+0GAgHBwf9CNjabtrFu7y9lZfA05nHn9ic2AAAAIeGgnd1 + c3BvAwMDA3KDj49ptDkFCgoKCgoKCgcN227ak7y8lJbSwcKayNicnAAAAIWIrHVzcG8DAwMDcq2Njz5e + Bis5aGhoaO05CgoG+9vapNS8vZWXwNOZx9fYLgAAAIGIdXRwb28DAwNyq7OPW14raLTk8ujo6PLo7TkK + BQtA2tW8vJSW0sHDx9fXLgAAAIOGdXNwbwMDAwOrfo+4tAZeVvLo7+7u7u7u6PLtCgUNo9q5vL2Vl8HC + mp/Y2C4AAIKGc3BvAwMDA3Z6s5GLOWhW8u/u7u7m5ubu7uP17Qr9nNrFvL2+l9LTmcht2C4AAIKGc29v + AwMDcqutjJJWBj/37+7u5t2mps3d5u7P92gFM9psvLyUltLBw8fY2AAAAH6GcG8DAwMDdq1+kZBkCrfo + 7u7mpqutzq2rzebu5eQ5GGyku7y9ldLBwprDnAAAAIKEcG8DAwNyen6vkWleCuDp5ubNeq/f39/Oq93u + 0PdoGKDau7y9lZfAm54hMwAAAISxbwMDAwOren6DkWk0Cvfq5uapft+z4bPfrabm5/btGKDaury9vMlA + LCD9LAAAALOEqwMDA3J6fq+EkWk/Cvfq892rr7H5+eHfzqjm+OtkDWza1cUxDCH7CyD9LgAAAACDfgMD + A3Z6foOEj2I/C/Lw9N12zrHh+eHfzqjm+Om3LEE2+hgI+yA0Xl4LHgAAAACEhAMDA6utr4OxjGoEBGTv + +M2mrYOxsd+ves3n8+sLCPsgNDQ4ODtWZkw+AAAAAACxhHYDcnp+r4Sx4TMuPz/y8fjOq62vr696qObx + 87c4ODc6ZE9PTkxHQkpqAAAAAAAAg34Ddnp+g4SGWQxATzTi5Or4zqurq6lz5u7x7Ds+UVpMVUJCQkJC + Qj0AAAAAAAAAsYMDeoqxhIw+DB5SYmQ04uzr597erKro8uziNz5hW0ZCQkJCQkJCQmkAAAAAAAAAAIR+ + hIqMjWEECT5MUWJWaAtngIB/41VnCDQ7PmFRS0JCQkJCQkJCTQAAAAAAAAAAADKEg4qNjGAMMU1CS1Fc + XztoIPsICDRoN1ZSYVFLQkJCQkJCQlNTPgAAAAAAAAAAAACxsYqMjjT8akZCQklaYWI+Zzs3N1ZZaWJR + WklCQkJCQkJTRERMAAAAAAAAAAAAAAAAs7OMPiD+UUpTQkJETFpRUmJiYmJhUVpMREJCQlNTREREREYA + AAAAAAAAAAAAAAAAAOGxLCAsaVVERERTU1NHTT1XVz1NR1NTU1NEREREQ0VFRAAAAAAAAAAAAAAAAAAA + AACKX/0zW01HRERERERERERERERERERERERERERDVFRDAAAAAAAAAAAAAAAAAAAAAAAAKrRgSltaS0RE + REREREREREREREREREREQ0VUVEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAATVdbW1VGQ0NDQ0NDQ0NDQ0ND + RkVHVVVVMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATE09V0tKSklJSUhISEhISUlKVUxNgEsAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAA9PD1QPTxMS0tLS0tMTTw9PIA8AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAA9PVBXV1dXV1dXUD09AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////////3P3////////c/f//AAP//9z9//wAAH//3P3/8AAAH//c/f/AAAAH/9z9/4AAAAP/ + 3P3/AAAAAf/c/f4AAAAA/9z9/AAAAAB/3P34AAAAAD/c/fAAAAAAH9z98AAAAAAf3P3gAAAAAA/c/eAA + AAAAB9z9wAAAAAAH3P3AAAAAAAfc/YAAAAAAA9z9gAAAAAAD3P2AAAAAAAPc/YAAAAAAA9z9gAAAAAAD + 3P2AAAAAAAHc/YAAAAAAAdz9gAAAAAAD3P2AAAAAAAPc/YAAAAAAA9z9gAAAAAAD3P2AAAAAAAPc/cAA + AAAAA9z9wAAAAAAH3P3AAAAAAAfc/eAAAAAAD9z94AAAAAAP3P3wAAAAAB/c/fAAAAAAH9z9+AAAAAA/ + 3P38AAAAAH/c/f4AAAAA/9z9/wAAAAH/3P3/gAAAA//c/f/wAAAH/9z9//AAAB//3P3//AAAf//c/f// + gAP//9z9////////3P3////////c/f///////9z9KAAAACAAAABAAAAAAQAIAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8AAAAAAOqUTQB3dn4AODc7ADY1OQA+PUEAXl1hALq5vQA0MzYAV1ZZAKGf + owBramwAnBz/AJ0j+QCdIvEApDTsAKlN5gCpatAAtofRAMGd1ACaK88ArZC7AGVjZgCkPNAAu2LjALJY + 0ADBbc8ApEuxALastwCXOZsAS0lLAHNxcwCdRpkAu3G2AKRXmgDKer0AlDx8AItQcwDMjLIAqGeMANW5 + wQA6NjcAkY2OANGVogCqo6QAzqGkAJpZWQDFwsIA6KmhAIR/fgBRRkMA0aqeALazsgBbMh8AVjksAEI7 + OABkNh4AZDwoAKd0WgCqemMAtZqMAGFVTwDEvrsA1tTTAItNKgCPUCwAjU4rAJVULwCQUzEAklc2AJVa + OACVWzoAl14+AJ5pSwCgbVAAom9TAJVmTQCLYksAroBmAMu0pwDQu7AAjE4qAJxbMwCcZUIAfltFALKJ + cADAlnwAnoBuAL6eiwDFppQA2Ma7AM7JxgBZSD0Ao4l3AI14agDRu6wA1cO3ANfRzQBtTjcAs4hnAJl3 + XgCHaVMATUA2AMeunAC/sacA0MjCAOni3QC9ubYA7urnAOmUTQDok00A4o9LAOqUTgDnk00A5pNOAOSS + TgDrl1EA4ZFOAN6QTwDajk8A7J1aANWNUQC4flAA1ZReAO2laQCxfE8AqXZMAN6cZQDkomsA7ax2AO+x + fgDbpnoA67OEAOGtggDsvZUAz6eFAPTKpwCskHkA9NCyAPXStQDfwakA8ta/AN3HtQDz28gA7dbDAOXZ + zwDaz8YA2c7FANjNxADXzMMAzMO8AMrBugDIv7gA1s3GALGrpgDJw74Ax8G8AL23sgDUzskA0szHAMvF + wADj3dgA7efiAMC7twDolEYA6ZZKAOmXTADqmE8Az4dIAOuaVADimFUA7KJhAM6NVQDtq24AypZpAPC5 + hgDKn3kA8cCUAHBZRQDSro4A1LKUAH5wYwDjzLgA39TKAN7TyQDd0sgA3NHHANvQxgDaz8UA4tfNANPJ + wADSyL8AzcS8AMzDuwDJwLgA5dzUANrRyQDFvbYAwrqzAN7WzwDc1M0A18/IANHKxADllEEA66hkAMyU + XwDTnGgA4dbLANXLwQDPxr0A3dTLAOng1wDKxL4AubWxALWxrQDz7ukA7enlAM/MyQD29PIA4pA3AN+e + WADvtnsAjnJUAPLHmAB5aVcAqnQxAHtaMQC4lWwA3I0oANqeTQCbaSAAuoIxAMWLNQC4ikoAl3ZIAFpI + LQDQhRIAsnYVAMGIKgDVljIAhV8lANebPwDhrFYAiWo6AKiDSgCKbD0A47NpAPLTowB9fYYAUVFVAM7O + 1ABFRUcA29vgAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtbVm + ZmzJocxdAAAAAAAAAAAAAAAAAAAAAAAAAAAAtomJ4MRsysrLoWuinQAAAAAAAAAAAAAAAAAAAAAAibKJ + ZTbFvL2VlqLLa2vbnTEeAAAAAAAAAAAAAAAAALWwsGVexbu8vL2UlphdotaiMaUAAAAAAAAAAAAAAACw + z66JLGy/0bm5u7y9lJfS05nInqXXAAAAAAAAAAAAss+ue+QM1b+/0dG6u7y9lZfBwp/XQG0AAAAAAAAA + ALDQe3l3qgQN2pOTk7/Ruru8vZfSwsTY19fYAAAAAAAA0NB5eHV1tyE22ZOTv7+5ury8lZfBw8jYbdcA + AAAAAK6BeXd1c3BvcfoIo2yTk9HRuru9vtLBmcfY2B4AAACF0Hl4dXNwbwMDfDMLXaSkbMW5ury9l9LD + mjbY2AAAAH2Gd3VzbwMDA2+Kj5z6Mwzbbm6ku7yUlsHCyNec2AAAgYd1dHBvAwNvg4+4tBgHB/0I2m7F + u72VwNPHn5zYAACIrHNwAwMDco2PXgY5aGhoOQoG+9qkvL2XwJnH2C4AAIh1cG8DA3Krj1sraOTy6Ojo + 7QoFQNq8vJbSw8fXLgAAhnNvAwMDerOLOVby7u7m5u7u9e39nMW8vpfTmW3YAACGc28DA3KtjFYG9+/u + 5qam3ebP9wUzbLyUlsHD2NgAAIRwAwNyeq+RXgrp5s1639/Oq+7QaBjau72VwJshMwAAsW8DA6t6g5E0 + CurmqX6z4d+t5uftGNq6vbxALP0sAACDfgMDen6Ejz8L8PR2zuH5387m+LcsNvoI+zReCx4AAISEAwOt + r7GMBATv+KatsbGveufzCwggNDg4VmY+AAAAAIMDdn6DhllAT+Lk+M6rq3Pm8ew+UUxVQkJCQgAAAAAA + sQN6sYQ+DFJiNOLr596s6PLiN2FbQkJCQkJCAAAAAAAAhIONjAwxQktcX2ggCAhoN1JhS0JCQkJCUz4A + AAAAAACxsYyO/GpCQlphPmc3N1lpUVpCQkJCU0RMAAAAAAAAAAAAsSwsaUREU1NHTVdXTUdTU0RERENF + RAAAAAAAAAAAAACKXzNbR0RERERERERERERERERDVEMAAAAAAAAAAAAAAAAAAE1bW0ZDQ0NDQ0NDQ0ZH + VVUwAAAAAAAAAAAAAAAAAAAATD1XSkpJSUhISElKVU2AAAAAAAAAAAAAAAAAAAAAAAAAAAA9PVdXV1dX + UD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA///////gD///gAP//gAAf/wAAH/4AAAf8AAAH+AAAAfgAAAHwAAAA4AA + AAOAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAADwAAAB8AAAAfgAAAH4AAAD/gA + AB/4AAA//wAAf/8AAf//4A////////////8oAAAAEAAAACAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wAAAAAA6pRNAHd2fgA4NzsANjU5AD49QQBeXWEAurm9ADQzNgBXVlkAoZ+jAGtq + bACcHP8AnSP5AJ0i8QCkNOwAqU3mAKlq0AC2h9EAwZ3UAJorzwCtkLsAZWNmAKQ80AC7YuMAsljQAMFt + zwCkS7EAtqy3AJc5mwBLSUsAc3FzAJ1GmQC7cbYApFeaAMp6vQCUPHwAi1BzAMyMsgCoZ4wA1bnBADo2 + NwCRjY4A0ZWiAKqjpADOoaQAmllZAMXCwgDoqaEAhH9+AFFGQwDRqp4AtrOyAFsyHwBWOSwAQjs4AGQ2 + HgBkPCgAp3RaAKp6YwC1mowAYVVPAMS+uwDW1NMAi00qAI9QLACNTisAlVQvAJBTMQCSVzYAlVo4AJVb + OgCXXj4AnmlLAKBtUACib1MAlWZNAItiSwCugGYAy7SnANC7sACMTioAnFszAJxlQgB+W0UAsolwAMCW + fACegG4Avp6LAMWmlADYxrsAzsnGAFlIPQCjiXcAjXhqANG7rADVw7cA19HNAG1ONwCziGcAmXdeAIdp + UwBNQDYAx66cAL+xpwDQyMIA6eLdAL25tgDu6ucA6ZRNAOiTTQDij0sA6pROAOeTTQDmk04A5JJOAOuX + UQDhkU4A3pBPANqOTwDsnVoA1Y1RALh+UADVlF4A7aVpALF8TwCpdkwA3pxlAOSiawDtrHYA77F+ANum + egDrs4QA4a2CAOy9lQDPp4UA9MqnAKyQeQD00LIA9dK1AN/BqQDy1r8A3ce1APPbyADt1sMA5dnPANrP + xgDZzsUA2M3EANfMwwDMw7wAysG6AMi/uADWzcYAsaumAMnDvgDHwbwAvbeyANTOyQDSzMcAy8XAAOPd + 2ADt5+IAwLu3AOiURgDplkoA6ZdMAOqYTwDPh0gA65pUAOKYVQDsomEAzo1VAO2rbgDKlmkA8LmGAMqf + eQDxwJQAcFlFANKujgDUspQAfnBjAOPMuADf1MoA3tPJAN3SyADc0ccA29DGANrPxQDi180A08nAANLI + vwDNxLwAzMO7AMnAuADl3NQA2tHJAMW9tgDCurMA3tbPANzUzQDXz8gA0crEAOWUQQDrqGQAzJRfANOc + aADh1ssA1cvBAM/GvQDd1MsA6eDXAMrEvgC5tbEAtbGtAPPu6QDt6eUAz8zJAPb08gDikDcA355YAO+2 + ewCOclQA8seYAHlpVwCqdDEAe1oxALiVbADcjSgA2p5NAJtpIAC6gjEAxYs1ALiKSgCXdkgAWkgtANCF + EgCydhUAwYgqANWWMgCFXyUA15s/AOGsVgCJajoAqINKAIpsPQDjs2kA8tOjAH19hgBRUVUAzs7UAEVF + RwDb2+AA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAtonEystrnQAAAAAAAAAAsGXFvL2WXdYxAAAAAAAA + z3sMv9G6vJXBn0AAAAAA0Hl1tzaTv7m8lcHIbQAAhXl1cAN8C6RsubyXwzbYAIF1cANvj7QH/drFvcDH + nACIcANyjyvk6OgKQLyWw9cAhm8DrVb37qbdzwVslMHYALEDq4M06qmz3+bt2r1A/QCEA62xBO+msa/n + CyA4Vj4AAAOxPlI0697o4mFCQkIAAACxjPxCWj43WVFCQlNMAAAAAIozR0RERERERENDAAAAAAAAAD1K + SUhISk0AAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA8B8AAOAPAADABwAAgAMAAAABAAAAAQAAAAEAAAAB + AAAAAQAAAAEAAIADAACAAwAAwAcAAPAfAAD//wAAKAAAADAAAABgAAAAAQAgAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAHAAAADQAAABIAAAAUAAAAEgAAAAwAAAAFAAAABAAA + AAQAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAANAAAAGgAAACoAAAA8CwoJTx0ZFWElHxpmEw4KYAAA + AEwAAAA0AAAAMgAAADQAAAArAAAAIAAAABQAAAAKAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAFQAAACwKCQlPOzQtfXRjVKujinPQupp/6aqF + ZveOaEf6hmZL+IlzYeaBeXLBh4N/wYeDf8VwbGmwSEdFjiAfHmkAAABFAAAAKQAAABUAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAABUAAAAzJyMeanppWrC8n4Xn3LWT/9yy + jf+thWL/bkst/29UPf+pmo3/1c3F/+Lc1v/j3NX/4dnT/97X0f/b1dD/19LN/8fBvPqhnJjcZGFfqCAf + H2kAAAA2AAAAGAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAKCYiHmiQe2rA07GU+tuy + jv/VqIH/vJBr/3dZQf9dUEj/o5qT/+DY0f/n4Nn/39bP/9zUzP/a0sv/2dHK/9XOyP/Tzsn/0szG/9DK + xf/SzMf/087J/8C8ufqDgH3FKikodAAAADIAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMHBgZFcWFTqNGw + k/jasY7/y512/8+edP+uglz/WUMz/2hnaf/LxsP/593U/93SyP/az8b/2c7F/9jNxP/WzMP/1MrC/87H + wf/Uy8T/08rE/9HKxP/QycP/zcfD/8rFwf/OyMT/wb26/nl3db0UFBNaAAAAHQAAAAQAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAGSUg + G2SrkXnZ3raT/8ygev/HlGn/0pxt/6t+V/9PPTH/eHl9/97Vzv/j18z/29DG/9zRx//b0Mb/2tDG/9nP + xf/YzsT/1cvC/83Gv//Oxr//zcW+/83Fvf/NxL3/zMfB/8zGwf/Jw77/xcC8/87Kx/+rqKXuPj08hwAA + ACkAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAEAAAAbPjQsfMinifLWq4b/ypZr/8iNW//SlF7/rHxS/049Mf99f4P/5t3V/+LWy//d0sj/3dLI/9zR + x//c0cf/3NHH/9vQxv/az8b/2c/F/9fNw//UysH/0Me+/8zEvf/Jwrz/x8C5/8e/uP/Jwbv/yMS//8bC + v//Ev7v/wLy5/2BfXasAAAAzAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAABhHPDGF0KuJ+8+heP/JkF//zIlR/9SRWP+6h1z/VkI0/3J0ef/o4Nj/5NjN/+DV + y//g1Mr/39TJ/97Tyf/d0sj/3NHH/9zRx//c0cf/29DG/9rPxf/YzsT/18zD/9TKwP/Qxr7/zcS8/8nA + uP/BurP/w765/8fAu//Ev7v/wLu4/8TAvf9ycG+9AwMDNQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAED4zKnvOp4P8zJtw/8yPXf/Oi1L/0otP/8yPW/9rUT3/RUZM/8/J + xf/q3tL/4tfN/+LXzP/h1sz/4dXL/+DUyv/f1Mn/3tPJ/93SyP/c0cf/3NHH/9vQxv/az8b/2M7E/9bM + w//UysH/0ce//87EvP/Hv7j/wLq1/763sP/Cu7b/w766/766tv/EwL3/cG9tuwAAACoAAAACAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGJR4YXsSbePXMmGr/zZJg/9COV//Si1D/3ZFS/59n + OP9IQT7/V1li/9nU0v/y59z/49fM/+TYzv/j2M3/4tfN/+HWzP/g1cv/39TK/97Tyf/d0sj/3NHH/9zR + x//b0Mf/2s/G/9nOxf/WzMP/1MrB/9HHv//OxLz/yMC4/8C6tP+7ta//vrex/8G8uP+9ubX/wb27/1ta + WaUAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJBwY0poJj38+XaP/PlWT/0pJb/9KL + T//YjU//4ZJR/7t4P/9cTUP/bnF9/4iHjv/a1tL/9evh/+XZzv/k2c7/5NjO/+PYzf/i18z/4dbL/+DV + y//f1Mr/3tPJ/93SyP/c0cf/3NHH/9rQxv/ZzsX/18zD/9TKwf/Rx77/zcS8/8nAuP/BurT/urSv/7q0 + rv+/urb/vLm2/7q2tP81NTR3AAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9tVUCp0plo/86V + ZP/VmGT/1IxP/9mOT//dj0//4JFO/+eUTv+uc0L/c25x/29yfv9YWF3/ube1//jv5v/o3NH/5dnO/+TZ + zv/k2M7/4tfN/+LWzP/g1cv/39TK/97Tyf/d0sj/3NHH/9zRx//a0Mb/2c7E/9fMw//UysH/0Me+/8zD + u//Iv7f/wbmz/7q0r/+4sq3/vbi0/767uP+fnZvrDQ0NOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYe + FlfFj2H8zZFd/9mgcP/VjVH/2Y5O/96QT//hkU7/45JO/+aTT//slUz/s3dF/3hydf9scH3/TExR/5eV + lf/z6+T/7eHW/+XZzv/l2s//5NnO/+PYzf/i18z/4dXL/+DUyv/e08n/3dLI/9zRx//c0cf/2tDG/9nO + xP/WzML/08nA/8/Gvf/Lwrr/xr62/8C4sv+3sq7/trGr/7u3s//Bvrv/ZGNhrAAAAAwAAAAAAAAAAAAA + AAAAAAAAAAAAEIlkRcLTkFj/2aJ0/9iUW//ZjU3/3pBP/+GRTv/kkk7/5pNO/+eTTf/plE3/8JZM/8R/ + Rv+CdXH/dHqI/1FSWP91dHb/493Y//Ln3P/m2c//5drP/+TZzv/j2M3/4tbM/+HWy//g1Mr/3tPJ/93S + yP/c0cf/29HH/9rPxv/YzsT/1cvC/9LIwP/Oxbz/ysG5/8W8tf+9trD/trCs/7Wvq/+8uLX/sK2r+hoa + GUoAAAAAAAAAAAAAAAAAAAAAKSAYUcuOWf7VmGX/3aJx/9mNTP/ekE7/4pFO/+SSTv/mk07/55NN/+mT + Tf/plE3/6pRN//GXTP/YiUf/lHlo/3p/jf9cXmf/XFxf/8nEwf/27OL/6NzS/+bb0f/l2tD/5NjO/+LW + y//g1cr/39TK/97Tyf/c0cf/3NHH/9vQxv/az8X/183E/9XLwf/Rx7//zcS7/8i/uP/CurP/urSv/7Su + qf+1r6v/w7+8/2ZkY6oAAAAFAAAAAAAAAAAAAAACeFk+ptuXXf/eqHr/3JVa/96PTf/hkU7/5JJO/+aT + Tv/ok03/6ZNN/+mUTf/qlE3/6pRN/+qUTf/vl03/6ZFH/7F/Wv+CfoX/Z2p0/05PU/+op6b/+vby//Tv + 6v/v6+b/7ujj/+rj3v/m3dX/4dfM/97TyP/e0sj/3NHH/9zRx//b0Mb/2c7F/9fMw//UycD/0Ma9/8vC + uv/Fvbb/v7ex/7eyrv+xq6b/ubWx/6ajofEMDAwsAAAAAAAAAAAFAwEevIle6NybZf/hqXj/3Y5M/+GR + Tv/kkk7/5pNO/+iTTf/pk03/6ZRN/+qUTf/qlE3/6pRN/+qUTf/qk0v/7JNJ//OpbP/YuaH/npqe/3h6 + hP9OTlT/hoaH/+rn5f/x7en/9PHt//r28v/28e7/7enk/+be1//f1Mr/3dHH/9zRx//c0cf/2s/G/9jO + xP/Wy8L/0si//87EvP/IwLj/w7u0/7u1r/+0r6r/r6ql/7+7uP8+PDxuAAAAAAAAAAAuIhdQ3Z5p/+Cm + df/hnmX/4I9L/+SSTv/mk07/55NN/+mTTf/plE3/6pRN/+qUTf/qlE3/6pRN/+qSSf/rl1L/772T//fc + x//94cr/z7Sc/4yIjP+FhpH/XV1k/1lZXf9paGv/dXR2/5mXmP/QzMr/9fHt//Xx7v/q5eD/4NbM/9zR + xv/c0cf/29DH/9rPxf/XzcP/1MrB/9DHvv/Lwrr/xr62/7+3sf+4s67/sKql/7q2sv9zcW+vAAAAAQAA + AABkSzaF6KRr/+Wuf//gk1P/45FN/+WSTv/nk03/6ZNN/+mUTf/qlE3/6pRN/+qUTf/qlE3/6pJJ/+ua + WP/xyKf/++PP/+LEq/+Yfmj/aVtU/1xbYP9TUln/R0dN/zc2O/81NDn/ODc8/zk5Pf9HR0z/f31//9jV + 0v/49PH/7Ofj/+DWzf/c0Mb/3NHH/9vQxv/ZzsX/1szC/9LIwP/OxLz/ycC4/8K6s/+7tbD/tK6q/7Gs + qP+Wk5HdBAQEFAAAAACQbEyw6qZt/+etff/ij0v/5ZJO/+eTTf/ok03/6ZRN/+qUTf/qlE3/6pRN/+qU + Tf/qk0v/65hU//HIpv/748//yauR/3JeUf9NS1H/OTtC/y4uM/8rKzL/LS0z/zAwNf8xMDT/Li81/y8w + Nv80NDj/Nzc8/1JSVf+8ubj/+PTw/+zm4v/e1Mr/3NHG/9vQxv/az8X/183D/9TKwf/Qxr7/y8K6/8W9 + tf++t7H/t7Kt/7Cqpf+mop/2GRkZMS0tLAmugFnM6qly/+ipdP/jj0r/5pNO/+iTTf/plE3/6ZRN/+qU + Tf/qlE3/6pRN/+qUTf/rlU3/77uR//vj0P/FqI//ZVZN/0NFTf89Njb/S0A7/1FFO/9ZSTT/X0ww/19M + MP9iTC3/YUop/0U8Mf80MjX/MDA1/zY2O/9HR0z/vru6//fz8P/o4dz/3NHG/9zRx//b0Mb/2M7E/9XL + wv/SyL//zcS8/8i/t//BubP/ubSv/7Osp/+uqaX/Ly4uSzo2NBbAi1/d66p1/+ika//lkEr/6JNN/+mT + Tf/plE3/6pRN/+qUTf/qlE3/6pRN/+qVT//tp23/+N3H/9zBqv9tWUz/Q0VN/0s+N/9oUUH/blMx/4lg + IP+kbhj/s3YT/7d4E/+wdRb/r3QV/5JlHP9cSSv/QTkx/zAwNf82Njv/Wlpc/+Hd2f/y7er/4djP/9vQ + xv/b0Mb/2s/F/9fNw//TycD/z8a9/8rBuf/Du7T/u7Ww/7Wvqf+1r6v/Ojk4Xzw6OBzEjV7l7Kt0/+ig + ZP/mkUr/6JNN/+mUTf/qlE3/6pRN/+qUTf/qlE3/65ZQ/+yaVv/xw53/9+HO/5J2X/9MSk//RTs4/3JX + Qf93Vyn/n2sW/8B9D//Ngwv/0oYM/9SHDv/Vhw3/0YYM/8uCC/+kcBv/cVcu/0k+L/8xMDX/ODg9/5SR + kf/28+//6OLc/9zRxv/c0cf/2s/G/9jOxP/Vy8H/0ce+/8zDu//Gvbb/vbex/7myrf+4s7D/Ozo4Zzw7 + OhvGjV3m7apz/+meYf/nkUr/6ZRN/+qUTf/qlE3/6pRN/+qUTf/qlU7/7JpX/+2kaP/32sH/1ruk/2ZU + SP89PkX/Z04+/39dLf+lbhX/yYEN/9SHDv/Zihv/3o0o/+COLv/gjS3/3Ywk/9iJFv/Thgr/snwk/3th + OP9EOi7/MjI4/1taXP/h3Nn/8Ovn/97Uyv/c0cb/29DG/9nOxf/WzML/0si//83EvP/Iv7f/v7mz/7u2 + sv+4sq7/PTs5ZD07OhLHkGHe7qpx/+qfYf/okkr/6ZRN/+qUTf/qlE3/6pRN/+qUTf/rmFT/7J1d/++x + fv/75NH/rY92/1NMTP8+Oj3/dFs+/51qGv/IgQ3/1ogQ/92NJ//lkT3/6pZL/+uZUf/rmFD/6ZRH/+OP + N//bix3/1IcM/7CEPf9vWDf/ODQy/0JCR/++urb/9fHu/+HY0P/b0Mb/29DG/9rPxf/XzMP/08nA/8/F + vf/JwLj/w7y1/8G5s/+7ta//PDs5VTQ0NQW9i2HN8alu/+ufYf/okkr/6pRN/+qUTf/qlE3/6pRN/+uW + UP/snVv/7aFj//C7j//449H/knVd/0tKT/9EPj3/gmAr/8eBIv/ViBD/3Iwk/+eTQ//snFf/7aVk/+2q + av/tqWn/7aNg/+uYUf/kkDn/2okW/8yMJP+bekn/UUMy/zk6P/+loZ7/9/Pv/+Tb0//c0Mb/3NHH/9rP + xv/XzcT/1MrB/9HHvv/SycD/x8C5/4mFgf+fmpX/MS8uOwAAAACogmOx9apt/+yiZf/pkkr/6pRN/+qU + Tf/qlE3/6pRN/+uaVv/toWL/7qVp//LBmP/1387/hGlU/0hJUf9KQDb/mmsh/9iRL//Zihr/45A4/+yb + Vf/tqWn/77Z6/++8g//vu4H/7rJ0/+2kYv/qlkz/340q/9WLGP+3j1D/cFk2/zw6O/+bmJf/9vLv/+Tc + 1f/c0Mb/3NHH/9vQxv/c0cf/3dPI/9TLw/+rpqT/X15e/0FBQP+SjYnvGxoaHAAAAACDaVOD+a5w/+yl + af/pkkn/6pRN/+qUTf/qlE3/65ZQ/+ydXP/upGj/76lv//LCm//14M//gmlV/01OWP9RRDL/pnEd/9ua + Ov/cjiX/55JD/+2iX//us3b/8cON//LPnP/yzJn/8L6F/+6tbv/snFb/4482/9iNG//JnVf/hmUy/0Q/ + Ov+in53/9/Pw/+fe1v/i18v/5NnO/97Uy//Kwrz/o5+f/3d2e/9UVVv/Pz9B/1NSUf+Mh4PMBgUFAgAA + AABKOixJ+bF2/+2mbP/qk0v/6pRN/+qUTf/qlE3/65hU/+2gYf/up23/76x1//LAl//45dT/iXFe/1tc + Zv9XRzL/qHAW/9yhQ//fkzH/6JNF/+2lY//vt3v/8cmV//PZrP/z1aX/8MKM/+6wcv/snlr/5I84/9uS + J//SpVv/jWUl/09MSv/Avbn/+vbz/93W0f/Dvbr/paGj/4OCh/9jZGv/S05V/01QV/9UVVv/PkBB/2Zk + Yv9zb2yRAAAAAAAAAAATDQgS46Ry7PGpbf/rmFL/6pRM/+qUTf/qlU7/7JxZ/+2jZv/vqnL/8LB8//K8 + j//85tT/gXl1/4OGlf9oWUf/nmcO/9iePf/in0T/5pA+/+2iX//us3b/8MOM//LOnP/yy5j/8L2F/+6t + bv/snFb/4o0y/96bPP/LkTP/kG89/3d5g/+joqT/iImQ/2xvdv9aXWb/TlJa/0lKUP9HREf/ST48/1Y/ + Nv9uSzv/dltN/4eDgP9FQkBEAAAAAAAAAAAAAAAAqHxXpfqwc//snl3/6pJK/+qUTf/rllH/7J5d/+6m + av/vrXb/8LOB//e9jv/gxa7/enuE/6Oltv9lX1v/kGEV/82KHP/lrVn/5JI9/+uZU//tqWj/77V5/++8 + gv/vuoD/7rF0/+2kYv/qlkv/4I8v/9mSJ/++gR7/ZF5V/0xLUv9GQUL/RD09/0k7OP9NOTH/Uzgs/1w5 + KP9oPSf/dkQq/4dQM/+TVjT/mGlO/5SQjNwQEA8IAAAAAAAAAAAAAAAATDgmQvqydv/to2b/6pNL/+qU + Tf/rmFP/7Z9g/+6obv/wr3n/8raF//nAkP+Yhnn/nKCv/4lxa/9GPT7/b19G/6dwF//Xli3/6bBf/+ul + X//snFf/7aRi/+2qaf/tqWj/7qNf/+6aUP/kkDf/2okV/9WKEf+Fbkr/STUx/2hALP93UDz/dko0/3xL + Mf+CTzT/iFI1/4xSM/+OUjH/jlAu/4xNKv+KSCP/m3pn/2VlY3gAAAAAAAAAAAAAAAAAAAAABwQCAcqS + Y8X3rW//65lV/+qTS//snFr/7qVp/+6nbP/wsHz/+8CP/7aSdv+Qkp7/trfD/7+mmf9jPSv/R0NH/3Nm + UP+dbCf/yoUk/+OfRP/so1T/7ZtQ/+6aUP/umk7/5pJE/9CEMf/Uhhj/1ZAe/4t0Tf9HMSv/kGdS/9O+ + sv/CpZP/kVc2/49SMP+OTy3/jE0q/4pLKf+KSyn/i0wq/4tMKf+PUC7/nIyC6RscHBQAAAAAAAAAAAAA + AAAAAAAAAAAAAFc+KUT9s3b/7aBh/+qRSf/xuIj/9cuo//G5i//8zKX/y6eK/4mHjP+3u8n/rJaP/82y + ov+5nY7/XDcl/0VAQv9rY1j/mnJF/7p6Mv/KgzP/0og4/9KEN//HejL/oWYr/3daM/93Y0H/ZF1T/0Uw + K/+DWkT/1L6x/864qv+VX0D/h0Yk/4pMKf+KTCr/ikwq/4tNKv+LTSr/i00q/4lJJP+hcFT/X11abgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsfFSg/K9v/+uZVf/zwJb/9tO1//nYvf/xx6X/fG9n/7S4 + xf+9ucD/kVg4/5diQ//SvK//vqOU/2tDMP9DMy//S0xQ/2dfWP9/Z1H/jGpL/45qSf+DZkv/bWBT/1hX + Vf9HP0D/Ty0h/5BpVP/TvbD/0Lqt/51tUP+HRiP/i00q/4tNKv+LTSr/i00q/4tNKv+LTSr/i0wp/5NS + Lf+SemvGDA0OBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfFxAT4aFs3fSlY//xuoz/9tO2///W + tf+zj3P/dHV7/9TX4/+vkof/iUch/4dHI/+XY0T/yrGi/9C7r/+Xc2D/YT8v/0c0Lv9BOTr/Qz5A/0Q+ + P/9FOjn/RzIs/1QxIf93TTj/s5SE/9nGvP/KsKH/m2lL/4dHIv+LTSr/i00q/4tNKv+LTSr/jE0q/4xN + Kv+MTir/jUsm/51rTuY1MzEoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASDYnOPav + dPfzrnT/9s6u//vPqv93Y1T/jZCX/9za4P+dak//iUgj/4xNKv+ISCT/jlQx/7ONd//UvrH/zreq/6+R + gf+UcV//hmFP/4VfTP+LZFD/nnpo/72hkv/Wwrf/076x/7SQev+RVzX/iEcj/4tNKv+MTSr/jE0q/4xN + Kv+MTir/jE4q/4xOK/+MTSr/m1s3/0c1K0wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAG9WQlj9uYH+9reE/+i7lf9US0f/i46W/9nOzP+rfWL/iksm/4xOKv+MTir/iksn/4hH + I/+UXDv/rYVr/8Wnlf/Quav/1L6y/9XAtP/VwLP/0bqs/8Snlv+uhW3/ll8//4hIJP+KSib/jE4q/4xO + Kv+MTir/jE4q/4xOK/+MTiv/j08s/41OK/+YUy3/ZkAqjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0YE9h/8OP/tuleP9LRkX/V1ph/7ypof+0h27/h0Ug/4xN + Kf+NTiv/jE4r/4xOKv+KSyf/iEgj/4pMKP+RVzT/lmA//5lkRP+WYED/kVg1/4tNKf+ISCP/ikom/4xO + Kv+MTiv/jE4r/41OK/+NTiv/jU4r/41OK/+WVS7/mlcv/5lVLv9uPSGiCAQBAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd2dZWPTGoPNfT0H/ODo+/6OE + dP+odFb/qXpf/5FTMf+LSib/jU4r/41OK/+NTiv/jU4r/4xNKv+MTCj/i0on/4pKJv+LSif/i0wo/4xN + Kv+NTiv/jU4r/41OK/+NTiv/jU4r/41OK/+MTir/jk8r/5hXMP+eWjH/nVoz/2A2H5EAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9J + RDF+alqfWEIx0pxqTf2fZ0j/yaua/76ZhP+ZYD//jEwo/41OKv+NTir/jU4q/41OK/+NTiv/jU4r/41O + K/+NTiv/jU4r/41OK/+NTir/jU4q/41OKv+NTir/jU4q/45PK/+SVDD/nV43/6RjO/+aXTr0TC4dagAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAExIQBZtvVduobk3/rH1i/8CciP+ziXH/kFIv/5FUMf+RUzD/kFIu/5BR + Lv+PUS3/j1Et/49QLf+PUC3/j1Et/49RLf+QUS7/kFIv/5FTMP+SVTL/llo3/55jQP+nbEf/qm1J/4NT + OMAsHBMxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIjGzSDWUCwqXJT/614Wf+odVj/oWxO/51l + Rv+aYUD/mF49/5ddPP+XXDv/l1w7/5ZcOv+WXDr/llw7/5ddPP+YXz7/m2JC/59oSP+lb0//rnZV/7F4 + Vv+YZknWTjQlYQYFAwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANCQbOXNQ + PJukdFjntYNm/7WEZ/+wgGT/rHtf/6h3Wv+mdFf/pXJV/6VxVP+mclX/p3RY/6p4XP+vfWD/tIFj/7aC + ZP+uel33iV9Hu0s0J1oRDAkJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAJAwATOCMYT2NDMZGEXEXElmtS5qN1XPmmeV//qnxi/6t9Yv+leF7/pXdc/Ztu + VO+NYkrTcU05pUUtH2cXDAYmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAq6akD760ryPCt7E21sa9P9vK + wEPBtrA5wrexK7GrpxakoaACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//wAf//9z9//4AAH// + 3P3/+AAAH//c/f/gAAAH/9z9/8AAAAP/3P3/gAAAAP/c/f4AAAAAf9z9/AAAAAA/3P38AAAAAB/c/fgA + AAAAD9z98AAAAAAP3P3wAAAAAAfc/eAAAAAAB9z94AAAAAAD3P3AAAAAAAPc/cAAAAAAAdz9gAAAAAAB + 3P2AAAAAAAHc/YAAAAAAANz9gAAAAAAA3P2AAAAAAADc/QAAAAAAANz9AAAAAAAA3P0AAAAAAADc/QAA + AAAAANz9AAAAAAAA3P0AAAAAAADc/YAAAAAAANz9gAAAAAAA3P2AAAAAAAHc/YAAAAAAAdz9wAAAAAAB + 3P3AAAAAAAPc/cAAAAAAA9z94AAAAAAH3P3wAAAAAAfc/fAAAAAAD9z9+AAAAAAf3P38AAAAAD/c/f4A + AAAAP9z9/wAAAAD/3P3/gAAAAf/c/f/gAAAD/9z9//AAAAf/3P3//AAAH//c/f//AAD//9z9///wB/// + 3P3////////c/SgAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAFgAAACMAAAAiAAAAEwAA + AAkAAAAJAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAmIx8bWlpMQI57ZVG2dFhBzGdO + OsxcUEWvWFVTllZUUZY4NjV4FBMTTAAAACIAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEBAUwTkM7h6KIcdTTq4j9tI1t/4dp + UP+mkoD/1svB/+Xd1f/k3Nb/39nT/9bRzP/AurX2j4uIzkRDQocFBQU2AAAABQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAROjIscayRed3bsYz/1qR6/4Vj + SP93b2r/ysS+/+Xd1f/e1Mz/2c/H/9bNxf/RysT/1MzF/9TNxv/W0cz/0MvH/6Gem+NAPz6DAAAAHAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBIG9eT6fUrYr/1KB0/8yU + ZP95WkH/gX19/9/Xz//h1sv/29DG/9vQxv/az8X/1szD/9DIwP/Oxr//zMS9/8vEvv/LxL//zsnF/8rG + w/94dnTBCwsLOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBCKHcFvA2qqB/82Q + XP/SkVr/gl9C/358ff/o3tb/49fM/97Tyf/d0sj/3NHH/9zRx//b0Mb/2c7F/9XLwv/Qx7//zMO7/8e+ + t//Evrn/x8K9/8vHw/+VkpDeFBQURAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEUgGhSvNik + eP/Njln/1o1Q/6FwR/9MS03/2tTP/+re0v/i18z/4dbM/+DVy//e08n/3dLI/9zRx//b0Mf/2s/F/9fN + w//TycD/zcS8/8S8tv++uLL/wbu2/8jEwP+XlJLiDg4ONAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVtI + N5XWoHH/0ZNe/9OMUP/gklD/k2I5/1hdZ/+ysLL/8Obd/+nc0f/j2M3/4tfN/+HWy//f1Mr/3tPI/9zR + x//c0cf/2s/F/9fMw//TycD/zcS8/8S8tf+7ta//vbez/8fDwP9+fHvJAQEBFAAAAAAAAAAAAAAAAAAA + AAAjGxVLxZBj/NaZZ//Wj1P/245O/+KST//fjUn/lHNb/2FndP+GhYj/5d3W/+7i1v/k2M7/49jN/+LW + zP/g1cv/3tPJ/93Sx//c0cf/2s/F/9fMw//SyL//zMO7/8S8tf+6tK7/urWx/8PAvf9IR0aGAAAAAAAA + AAAAAAAAAAAACIpjQcTdn2v/2ZVd/9yOTf/hkU7/5ZJO/+uVTv/mkEj/onte/2Jmcv9sbnT/0MrE//Pn + 3P/l2c7/49fN/+LWy//g1cr/3tPJ/93Sx//c0cf/2c/F/9bMwv/Rx77/ysG6/8G6s/+3saz/vbi0/6aj + ofANDQ0oAAAAAAAAAAAlGxJH0ZNf/92gbP/cjk3/4pFO/+WTTv/ok03/6ZRN/+yWTf/ulEn/vYZa/3Zw + c/9cX2f/s66r//Tr4v/t5Nv/6N/W/+TZz//g1Mr/3tLI/9zRx//b0Mb/2c7F/9TKwf/Pxb3/x7+3/722 + sP+0rqr/wb26/1BPTocAAAAAAAAAAHBQNJvnqHL/35he/+GPTP/lk07/6JNN/+mUTf/qlE3/6pRN/+uT + S//xk0X/3aV4/5+anP9hYmv/mJia/+vo5v/x7en/9/Pu//Lr5f/j2tH/3dHH/9zRx//b0Mb/183E/9LJ + wP/Mw7v/xLu0/7iyrf+4s67/kY+N1wICAgwdHR0NrX1U1uipdf/hklD/5ZJO/+iTTf/plE3/6pRN/+qU + Tf/qk0r/6pVP//TCmf/52sD/v6aR/358gv9bXGT/VVVZ/2RjZv+Ih4j/y8jG//fy7v/o4Nn/3NHG/9zR + x//az8X/1svC/9DGvv/Iv7j/vrex/7Suqv+tqab8IB8fOz01LjLRmGj156Rt/+OQS//nk03/6ZRN/+qU + Tf/qlE3/6pNL/+qVTv/2yaT/48q1/4NxZf9FQkX/Njc//zAyOv8sLTT/Kywz/yorM/88PEH/kI+P//Ds + 6f/o4Nn/29DG/9vQxv/YzcT/08nA/8zDu//Du7T/uLKt/7Wvq/9DQkFrXk0/UuGkcf/nn2T/5pFK/+mU + Tf/qlE3/6pRN/+qUTf/qkkr/87uN/+XNuP9vYFj/Pzs//01BOf9gSy//dFUo/3dYJ/92ViT/UEEt/zAv + Mv8tLjT/ioiJ//Xw7P/i2M//29DG/9nPxf/Vy8L/z8a9/8e+t/+8trD/ubOu/1xaWY1tWEdk56dx/+ic + Xv/okkr/6ZRN/+qUTf/qlE3/6pRM/+2hY//32L//j3tt/0E8P/9mTzr/i2Ii/7h4D//Ngwz/0YUO/86E + DP+0dhL/b1Qq/zg0MP83Nz3/wL27//Hq5P/c0cb/29DG/9fNw//RyL//ysG5/7+4sv+9uLP/ZWJgnW9a + SWPppm//6Ztb/+mSS//qlE3/6pRN/+qUTf/rl1L/9LyN/9nAq/9TSkf/Vkc+/5JmIv/HgAv/24sX/+KO + Lf/kkDf/4o8v/92LF//Fgxj/eV42/zAuLv95eHn/8+7p/9/Vy//b0Mb/2c7E/9TKwf/Mw7v/xLy1/8W+ + uf9nY2GaaFZIT+mmbv/qm1r/6ZNL/+qUTf/qlEz/65dR/+2eXv/5zaj/tp+M/0E+Q/9sVDP/w34X/9uL + GP/nk0L/7Z9c/+2mZf/toV7/6JRF/96MGv+7hzL/V0k2/1VVWP/m4t7/5NrS/9vQxv/Zz8X/1szD/9XL + wv/Gvrf/pJ6a/11aV4NLRD4t46Rv9uydW//qk0v/6pRN/+qUTf/snFr/7qVo//vTsv+mkID/Pz5D/4Zh + KP/akSb/4o4w/+2hXv/vt3z/8MKL/++5f//to2L/5I8z/9SSK/+GakD/U1BO/+Hd2v/p39b/4dbL/+LX + zP/WzMT/sKup/2NiY/9jYmD/RUJAWiYmJQjKk2XX8qNi/+qTSv/qlEz/65dR/+2hY//vqnD//NOz/6uY + iv9MSU3/kGUf/96cN//mkj3/7qlp//HFj//z2Kr/8ciT/+6rbf/mkT3/3Zo5/5x2Of9saGP/7Ojm/9zV + 0P+8t7T/lpWY/2hqcf9QUlr/P0BD/3Fua/cmJCMkAAAAAJJqSJf7rm3/6pRM/+qUTP/smlf/7qZq/++u + eP/9zqf/qaCc/3d5hf+LYBv/3Z86/+eXQv/tomD/8L2E//HLl//wv4f/7qdn/+aSPf/dmDD/l3Q8/25y + e/97fIH/Xl5j/05KTv9JPz7/UDw1/2pHNv+DX0z/dnJuwgYGBQEAAAAAPiwdPfmwdP/rl1L/6pRM/+yb + Wf/uqG//9baB/+Kyiv+Ylp3/enB1/2RSNf+/hCP/56ZL/+6iWv/upmX/769w//Gqaf/wm07/5pAh/8WI + Iv9fTDz/X0Y9/2RFNv9qQi3/dUYt/39JK/+ITCz/jkwn/59vVP9NTEtiAAAAAAAAAAADAQABwotdwvel + Yv/qllH/7612//Gwev/xuo3/o5KJ/7Cwu/+wlIX/UT44/2pbQ/+2fzL/4JhA/+qZSP/qlUf/2Ig9/7l5 + LP+0gS3/ZUw0/4trXv/Os6P/mWJC/45OK/+OTyz/jU4q/4xNKf+PTSf/jnNk3A4QEQoAAAAAAAAAAAAA + AABDMCA49qpr/O+kZf/20LH//tSx/7GXhP+or73/pYN0/7aOdf+yloj/WUI7/1VKRP9/ZEb/mm9E/5xu + Qv98X0L/U0tB/0w6Nf+MbF//1L2v/6R0Wf+HRiL/ikwp/4tNKv+LTSr/i0sm/51kRP9EPjpRAAAAAAAA + AAAAAAAAAAAAAAAAAACGYECA/7N0//jOrP/mv5//hX99/8fBxv+PUzL/iEgj/7KKcf/DqJn/jHFl/19M + R/9TQ0D/U0I9/1xFPP97XE7/tZiK/861pv+gb1P/h0cj/4tNKv+LTSr/jE0q/4xNKv+WUy7/a008kAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUEBAGwgVqp/8mT/7yYfP92eoD/zbmy/49RLf+KSib/iEcj/5pk + Rf+6lX//w6WT/76hkf++oZH/xKeX/8epl/+xiHD/kFU0/4hHI/+MTSr/jE4q/4xNKv+NTyv/lVIs/35K + LcgLCQgGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0MDAe/lXKtwJZ1/0dKUf+zmIv/mmA+/4pJ + Jf+MTSr/ikom/4lII/+PUi//l18+/5ljQ/+UWzr/jE4q/4hHI/+LTCj/jU4r/41OK/+MTSr/k1It/6Fb + Mv+ASCfPGA0HHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8ODgN9a1yCV0g81ppp + Tv+5j3f/q31i/45OKv+NTSn/jU4q/4xNKf+LSyf/i0om/4tLJ/+MTSn/jU4q/41OKv+NTSr/jk8r/5pa + NP+nZDv/ckQosREKBhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAARDgwIhVtCwbR/YP++lHz/nWVF/5JVM/+SVDH/kVMw/5FTL/+QUy//kVMv/5FTMP+SVTL/lVk2/59j + QP+sb0v/nGVF500xIW0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAGAgAJRSweY4NYQMendVj9rHha/6hzVP+jbk//oWpM/6BqS/+ibE3/pnBR/6t1 + Vv+qdVb/kmRJ2Fg6KX0RCgUZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEtAOS51X1JplXhnmamHc7i0j3rItZB7yauI + dL2bfGmhf2ZXdVRGPjstKikHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA//AP//+AAf/+AAB//AAAP/gAAB/wAAAP4AAAB8AAAAPAAAADgAAAAYAAAAGAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgAAAAYAAAAHAAAAD4AAAB+AAAAfwAAAP+AAAH/4A + AH//AAD//+AD//////8oAAAAGAAAADAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgMDIRcTED8QCgRGAAAALAAAACAAAAAVAAAAAQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAES0n + Ilp5ZVOolHZc2opsU/Kgi3j2qqCX5aijn9uSj4vMYl9dnSUkI1gAAAAUAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjHxtMknpmxterhP+rg2P/j4B0/8rAt//j29P/4tnR/9zU + zf/c1c//3NXP/8rFwP+Kh4TMJyYmWwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAktA + Nn3Hn3343qRy/5lwTv+Kgn7/39bP/+LXzP/b0Mb/2s/F/9XLwv/Qx8D/zcW+/8zFv//Szcj/wb26/1VU + U5gAAAANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0Q3h9OieP/Xk1n/o3FH/3x1cv/p4Nj/49fN/9/U + yv/e08n/3dHH/9vQx//ZzsT/08nB/83Du//Evbf/xcC6/8rFwv9mZGOsAAAACgAAAAAAAAAAAAAAAAAA + AAA2KyFhz5xw/9SSWf/dj03/f1s+/4GGkf/q4dj/69/T/+LXzP/h1sz/39TK/93SyP/c0cf/2c/F/9XK + wf/NxLz/wbqz/7+5tP/Hw7//UE9OjgAAAAAAAAAAAAAAAAkHBR2vgFnp3Jtk/9mOTv/kkk7/14pL/4Nv + Zf90eYP/08zF//Dk2P/k2M7/4tbM/+DUyv/d0sj/3NHH/9nOxf/UysH/zMO7/7+4sv+9t7P/trKv/R4e + HUEAAAAAAAAAAFg+KIXhomz/3JJU/+KRTf/mk07/7pZN/+OQS/+UdmL/ZWhx/7eyr//y5tv/6d7U/+TY + zv/f08n/3dLH/9zRx//YzsT/0si//8nAuf+7ta//wby4/3d1c7cAAAACFhUVD618U9vloGf/4ZBM/+eT + Tv/plE3/6pRN/+6US//uk0f/wp2C/3Z3f/+ioKL/6+bi//Hs5//z7OT/49nP/9zQxv/b0Mb/18zD/8/G + vf/EvLX/uLKt/6yopfgYGBgwRDcsRNmebf3jmFj/5pJM/+mUTf/qlE3/6pNL/+uVTv/4xJj/3b+m/4R7 + ef9PUl7/TE5V/2Jjaf+joaL/6+fj/+jf1v/b0Mb/2s/F/9TKwf/Lwbr/vbaw/7izr/9IR0ZzdFtFdemo + cv/lk1D/6ZNN/+qUTf/qlE3/6pNK//fDmP+7qJj/T0hJ/zw3OP9GPDD/SDwr/zwzK/8nKDD/aWls/+Xh + 3v/k2tH/28/F/9fNw//Pxr3/w7y1/7u1sP9raGaijm5Tj+2nbf/nk0z/6pRN/+qUTf/qkkr/8650/9S8 + p/9QSEf/XUk1/5ZnHf+9exD/xX8R/7V3Ef9sUSf/Kikr/4SEhv/x6uX/3NHH/9nOxf/TycD/yMC4/8C6 + tP96d3W4knBVju+maf/pk0z/6pRN/+qUTP/sl1L/+Mif/41+df9NQTr/pW8a/9iJEv/mkTH/6ZQ9/+WR + K//QiBj/clgw/0JCRv/a1tP/49nQ/9rPxf/Vy8L/z8a9/8rDvP99eXW1gmdSc/Gmaf/qlEz/6pRN/+uW + UP/vomP/8sqq/2pgXv9pUTH/1Ykb/+eTOv/vqWv/8LR5/+6lZf/nkS7/vIYw/05GPP/Bv77/6+HY/+HW + yv/f1Mr/xLy3/4eEgf9dWleYVUY7QOula/7rlU7/6pRM/+ybWP/wqm//8s2u/25nZ/9+Xir/4Zcw/+ua + Uf/xwIn/89Sl//C5gP/qlkT/1ZU2/3RhRP/Jycv/3NbS/7Wxr/+Ghon/WVpg/1RTU/9BPz1gHhwZCs2S + YNrynFX/6pRM/+2gYf/zsHj/68Oh/4WGkv+BZDb/3Zky/+2eUv/wtnv/8ceS//Gydv/rlj//z48r/21g + UP9ua3D/X1dX/1NEQf9XPTL/dUkz/4ZxZfAaGxsbAAAAAH5aPH/9qWT/65ZR/+6mav/2t4L/uqGR/6CZ + n/9eSz7/nnQx/+WeRv/xolX/8qJZ/+STRv/WjCX/h2Ar/4NqYf+KYEr/fkYo/4hLKv+MTCn/mFcy/2la + UJoAAAAAAAAAABcQCxTemF7l9q91//vQrP/Mq4//o6Gq/7KNev+kiHr/W0tE/3pdPP+kcz//pW88/3RW + Nv9eSTb/kHZt/8amk/+TVzX/i0sn/4xNKv+NTCf/lGNH8B0dHSAAAAAAAAAAAAAAAABSOiVM/biA//fN + q/+akYz/t6Wg/4ZDHf+lc1b/tJSD/4dya/9uXln/bVtU/4FpYP+ukoX/vJeC/5BUMv+JSib/i00q/4xN + Kv+XVjH/RDInWQAAAAAAAAAAAAAAAAAAAAAAAAAAd1pCbu22iP9xbnD/tJeI/4tJI/+IRiH/klY0/6l4 + W/+ziHD/tIx0/7GFbP+eaEn/iUol/4tLJ/+MTSr/kVEs/5xXL/9YMx2MAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAGZVRmBjVEjWoXVc/7CBZv+RVDH/i0om/4lII/+JRyL/iUgj/4lHIv+KSSX/jE0p/41O + Kv+aWTP/oV83/lEvG38AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCQcIck03qLaH + bP+td1j/ml06/5ZZNv+TVjP/k1Yz/5RYNf+ZXDr/omZD/6hsSf+HVzvHLx0URAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwdFjlpTDuOkWpTyqJ1XOmoeV/1qHle9qN1 + W+yWbFPRc1I/mzkpH0gGBQQCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAANfSzxnk3Ncr5d3YLNrU0B3IxsUEAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/wD/QfwAP0H4AA9B4AAHQeAAA0HAAANBgAABQYAAAEEAAABBAAAAQQAA + AEEAAABBAAAAQQAAAEEAAABBAAAAQYAAAUGAAAFBwAADQeAAB0HwAA9B+AAfQf4AP0H/wf9BKAAAABAA + AAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBEPKjYr + IGY8LyOBNjIuZywrKlIJCQklAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw4ld2FOoKKA + Y+2lj37/z8G0/9rRyf/QysT/s66p6WxqaKMSEhIuAAAAAAAAAAAAAAAAAAAAAAAAAAAnIRxFto1p58KP + Y/+ik4f/3dXO/+HXzf/b0Mb/1czD/9PLw//Vzsj/r6uo7zAvLloAAAAAAAAAAAAAAAAXEw8uu4xk7+GV + VP9/ZFD/xsbH/+/j1//h1cv/3tPJ/9zRx//XzMP/zMO7/8nCvP+3s7D7JSUlRwAAAAAEBggDiWREvOSc + X//kkUv/y4ZP/4N5df+5trX/7uLX/+XZzv/f08j/3NHH/9fNw//Kwbn/xb+5/5KPjdgFBQUPOy8kRNaX + Yv/kk0//6JRN//CWSv/ejk3/moh//6uqrP/n4dv/7+ff/+Xa0P/az8X/1MrB/8W9tf+8t7L/Pj08ZIVm + S5LqoGL/55JL/+qUTP/tlk7/8ruM/56Mgf9GSVT/SElQ/29xd//Tz8z/5tvS/9jOxP/Oxb3/wru1/3Vy + cLGvg1+67ZxZ/+qTS//rkkj/87SA/42Be/9WQy3/kGEX/51nE/9eQxn/T1BU/93X0//f1Mn/1MrB/8jB + uv+RjInStIZgue+bVv/qk0r/8Z1Y/9Wxlf9ZTEL/unkX/+yYNf/xnkj/3o8m/2ZOKP+hoKL/7OLY/9/U + yf/Nxb3/iYWBz5ZyVJDzn1j/6pRM//Wqbf/Hq5j/b1k3/+OTLv/xs3j/88mY//GhUf+tei3/np2c/9nU + 0f+opKP/cnBx/05MSqZNPC5A7p9d/+yWT//3sHX/xq6g/3doU//UkDP/97Fs//e9gP/xnkP/o3Ux/3Jm + ZP9sVEv/ZUEw/4JZQ/82MzFRCQkLAa52R7b/uX7/4riW/6ycmP+UeW3/e2JE/7J6QP+ucjz/h2E1/5l/ + bf+ebVL/iEci/5VQKf96UzzEBggJBQAAAAAkGA4j6bOE6buqnv+efG3/llg0/557av+IcWb/h3Bk/51/ + cv+kc1j/ikom/5JQKv+OUC7yIBcSKQAAAAAAAAAAAAAAAD0vJDR6aFrPp3xk/5haN/+NSyb/mFs5/5le + Pf+RUS7/jEok/5tZMv+QUzDpLBkORQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlU5KYaTZkzjll49/5Zb + Ov+WWjj/l108/41ZOulfOiWaDQUAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjYiFFbeo + n0zOuq5wz7qucrqqoVGTjYocAAAAAAAAAAAAAAAAAAAAAAAAAAD4H6xB4AesQcADrEGAAaxBAACsQQAA + rEEAAKxBAACsQQAArEEAAKxBAACsQQAArEGAAaxBwAOsQeAHrEH4H6xB + + + \ No newline at end of file diff --git a/lib/CefSharp.Dom.WinForms.Example/CefSharp.Dom.WinForms.Example.csproj b/lib/CefSharp.Dom.WinForms.Example/CefSharp.Dom.WinForms.Example.csproj new file mode 100644 index 000000000..c4749b378 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/CefSharp.Dom.WinForms.Example.csproj @@ -0,0 +1,26 @@ + + + + WinExe + netcoreapp3.1 + true + app.manifest + $(NETCoreSdkRuntimeIdentifier) + false + + + + + + + + + + PreserveNewest + + + + + + + diff --git a/lib/CefSharp.Dom.WinForms.Example/Controls/ControlExtensions.cs b/lib/CefSharp.Dom.WinForms.Example/Controls/ControlExtensions.cs new file mode 100644 index 000000000..a826ef5b4 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/Controls/ControlExtensions.cs @@ -0,0 +1,37 @@ +// Copyright © 2022 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System; +using System.Windows.Forms; + +namespace CefSharp.Dom.WinForms.Example.Controls +{ + public static class ControlExtensions + { + /// + /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread. + /// + /// the control for which the update is required + /// action to be performed on the control + public static void InvokeOnUiThreadIfRequired(this Control control, Action action) + { + //If you are planning on using a similar function in your own code then please be sure to + //have a quick read over https://stackoverflow.com/questions/1874728/avoid-calling-invoke-when-the-control-is-disposed + //No action + if (control.Disposing || control.IsDisposed || !control.IsHandleCreated) + { + return; + } + + if (control.InvokeRequired) + { + control.BeginInvoke(action); + } + else + { + action.Invoke(); + } + } + } +} diff --git a/lib/CefSharp.Dom.WinForms.Example/Program.cs b/lib/CefSharp.Dom.WinForms.Example/Program.cs new file mode 100644 index 000000000..b3471d1c3 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/Program.cs @@ -0,0 +1,46 @@ +// Copyright © 2022 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using CefSharp.WinForms; +using System; +using System.IO; +using System.Windows.Forms; + +namespace CefSharp.Dom.WinForms.Example +{ + public static class Program + { + [STAThread] + public static int Main(string[] args) + { + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + + var settings = new CefSettings() + { + //By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data + CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache") + }; + + //Example of setting a command line argument + //Enables WebRTC + // - CEF Doesn't currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access + // - CEF Doesn't currently support displaying a UI for media access permissions + // + //NOTE: WebRTC Device Id's aren't persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restart + settings.CefCommandLineArgs.Add("enable-media-stream"); + //https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-stream + settings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream"); + //For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180) + settings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing"); + + //Perform dependency check to make sure all relevant resources are in our output directory. + Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null); + + var browser = new BrowserForm(); + Application.Run(browser); + + return 0; + } + } +} diff --git a/lib/CefSharp.Dom.WinForms.Example/Properties/Resources.Designer.cs b/lib/CefSharp.Dom.WinForms.Example/Properties/Resources.Designer.cs new file mode 100644 index 000000000..321a615da --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/Properties/Resources.Designer.cs @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18034 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CefSharp.Dom.WinForms.Example.Properties { + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CefSharp.Dom.WinForms.Example.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap chromium256 { + get { + object obj = ResourceManager.GetObject("chromium256", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap nav_left_green { + get { + object obj = ResourceManager.GetObject("nav_left_green", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap nav_plain_green { + get { + object obj = ResourceManager.GetObject("nav_plain_green", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap nav_plain_red { + get { + object obj = ResourceManager.GetObject("nav_plain_red", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap nav_right_green { + get { + object obj = ResourceManager.GetObject("nav_right_green", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/lib/CefSharp.Dom.WinForms.Example/Properties/Resources.resx b/lib/CefSharp.Dom.WinForms.Example/Properties/Resources.resx new file mode 100644 index 000000000..641637ff5 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/Properties/Resources.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\nav_right_green.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\nav_left_green.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\nav_plain_red.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\nav_plain_green.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\chromium-256.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/lib/CefSharp.Dom.WinForms.Example/Resources/chromium-256.png b/lib/CefSharp.Dom.WinForms.Example/Resources/chromium-256.png new file mode 100644 index 000000000..a392e00fe Binary files /dev/null and b/lib/CefSharp.Dom.WinForms.Example/Resources/chromium-256.png differ diff --git a/lib/CefSharp.Dom.WinForms.Example/Resources/nav_left_green.png b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_left_green.png new file mode 100644 index 000000000..0433aada7 Binary files /dev/null and b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_left_green.png differ diff --git a/lib/CefSharp.Dom.WinForms.Example/Resources/nav_plain_green.png b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_plain_green.png new file mode 100644 index 000000000..95b73f0ee Binary files /dev/null and b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_plain_green.png differ diff --git a/lib/CefSharp.Dom.WinForms.Example/Resources/nav_plain_red.png b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_plain_red.png new file mode 100644 index 000000000..e160e0e71 Binary files /dev/null and b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_plain_red.png differ diff --git a/lib/CefSharp.Dom.WinForms.Example/Resources/nav_right_green.png b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_right_green.png new file mode 100644 index 000000000..410585caa Binary files /dev/null and b/lib/CefSharp.Dom.WinForms.Example/Resources/nav_right_green.png differ diff --git a/lib/CefSharp.Dom.WinForms.Example/app.config b/lib/CefSharp.Dom.WinForms.Example/app.config new file mode 100644 index 000000000..de8289320 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/app.config @@ -0,0 +1,3 @@ + + + diff --git a/lib/CefSharp.Dom.WinForms.Example/app.manifest b/lib/CefSharp.Dom.WinForms.Example/app.manifest new file mode 100644 index 000000000..219a5c562 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/app.manifest @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/CefSharp.Dom.WinForms.Example/crash_reporter.cfg b/lib/CefSharp.Dom.WinForms.Example/crash_reporter.cfg new file mode 100644 index 000000000..26d468ab1 --- /dev/null +++ b/lib/CefSharp.Dom.WinForms.Example/crash_reporter.cfg @@ -0,0 +1,12 @@ +# Crash reporting is configured using an INI-style config file named +# "crash_reporter.cfg". This file must be placed next to +# the main application executable. +# Comments start with a hash character and must be on their own line. +# See https://bitbucket.org/chromiumembedded/cef/wiki/CrashReporting.md +# for further details + +[Config] +ProductName=CefSharp.Dom.WinForms.Example +ProductVersion=1.0.0 +AppName=CefSharp.Dom +ExternalHandler=CefSharp.BrowserSubprocess.exe diff --git a/lib/CefSharp.Dom.Wpf.Example/App.xaml b/lib/CefSharp.Dom.Wpf.Example/App.xaml new file mode 100644 index 000000000..cde88db5f --- /dev/null +++ b/lib/CefSharp.Dom.Wpf.Example/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/lib/CefSharp.Dom.Wpf.Example/App.xaml.cs b/lib/CefSharp.Dom.Wpf.Example/App.xaml.cs new file mode 100644 index 000000000..08c178f1f --- /dev/null +++ b/lib/CefSharp.Dom.Wpf.Example/App.xaml.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using System.Windows; +using CefSharp.Wpf; + +namespace CefSharp.Dom.Wpf.Example +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + public App() + { + var settings = new CefSettings() + { + //By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data + CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache") + }; + + //Example of setting a command line argument + //Enables WebRTC + // - CEF Doesn't currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access + // - CEF Doesn't currently support displaying a UI for media access permissions + // + //NOTE: WebRTC Device Id's aren't persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restart + settings.CefCommandLineArgs.Add("enable-media-stream"); + //https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-stream + settings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream"); + //For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180) + settings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing"); + + //Perform dependency check to make sure all relevant resources are in our output directory. + Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null); + } + } +} diff --git a/lib/CefSharp.Dom.Wpf.Example/AssemblyInfo.cs b/lib/CefSharp.Dom.Wpf.Example/AssemblyInfo.cs new file mode 100644 index 000000000..8b5504ecf --- /dev/null +++ b/lib/CefSharp.Dom.Wpf.Example/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/lib/CefSharp.Dom.Wpf.Example/CefSharp.Dom.Wpf.Example.csproj b/lib/CefSharp.Dom.Wpf.Example/CefSharp.Dom.Wpf.Example.csproj new file mode 100644 index 000000000..83ff5fcc8 --- /dev/null +++ b/lib/CefSharp.Dom.Wpf.Example/CefSharp.Dom.Wpf.Example.csproj @@ -0,0 +1,24 @@ + + + + WinExe + netcoreapp3.1 + true + $(NETCoreSdkRuntimeIdentifier) + false + app.manifest + + + + + + + + + + + PreserveNewest + + + + diff --git a/lib/CefSharp.Dom.Wpf.Example/MainWindow.xaml b/lib/CefSharp.Dom.Wpf.Example/MainWindow.xaml new file mode 100644 index 000000000..0f234237f --- /dev/null +++ b/lib/CefSharp.Dom.Wpf.Example/MainWindow.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + GitHub.com - \ No newline at end of file + diff --git a/lib/PuppeteerSharp.TestServer/wwwroot/input/select.html b/lib/PuppeteerSharp.TestServer/wwwroot/input/select.html index 879a537a7..af9fb0aa5 100644 --- a/lib/PuppeteerSharp.TestServer/wwwroot/input/select.html +++ b/lib/PuppeteerSharp.TestServer/wwwroot/input/select.html @@ -16,7 +16,7 @@ - + diff --git a/lib/PuppeteerSharp.TestServer/wwwroot/table.html b/lib/PuppeteerSharp.TestServer/wwwroot/table.html new file mode 100644 index 000000000..2541f0b73 --- /dev/null +++ b/lib/PuppeteerSharp.TestServer/wwwroot/table.html @@ -0,0 +1,34 @@ + + + + + DevTools DOM Table Testing + + +

Table Testing

+ + + + + + + + + + + + + + + + + + + + + + + +
Header #1Header #2
Row #1 - Cell #1Row #1 - Cell #2
Row #2 - Cell #1Row #2 - Cell #2
Footer #1Footer #2
+ + diff --git a/lib/PuppeteerSharp.Tests.DumpIO/Program.cs b/lib/PuppeteerSharp.Tests.DumpIO/Program.cs deleted file mode 100644 index 8057f6ea2..000000000 --- a/lib/PuppeteerSharp.Tests.DumpIO/Program.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace PuppeteerSharp.Tests.DumpIO -{ - class Program - { - public static async Task Main(string[] args) - { - var options = new LaunchOptions - { - Headless = true, - DumpIO = true, - ExecutablePath = args[0] - }; - - var browser = await Puppeteer.LaunchAsync(options); - var page = await browser.NewPageAsync(); - await page.CloseAsync(); - await browser.CloseAsync(); - } - } -} \ No newline at end of file diff --git a/lib/PuppeteerSharp.Tests.DumpIO/PuppeteerSharp.Tests.DumpIO.csproj b/lib/PuppeteerSharp.Tests.DumpIO/PuppeteerSharp.Tests.DumpIO.csproj deleted file mode 100644 index 8b615dc56..000000000 --- a/lib/PuppeteerSharp.Tests.DumpIO/PuppeteerSharp.Tests.DumpIO.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - Exe - net5.0;net48 - - - - - diff --git a/lib/PuppeteerSharp.Tests/AccessibilityTests/AccessibilityTests.cs b/lib/PuppeteerSharp.Tests/AccessibilityTests/AccessibilityTests.cs index 53b36206d..0a64ce106 100644 --- a/lib/PuppeteerSharp.Tests/AccessibilityTests/AccessibilityTests.cs +++ b/lib/PuppeteerSharp.Tests/AccessibilityTests/AccessibilityTests.cs @@ -1,6 +1,4 @@ -using System; using System.Threading.Tasks; -using Newtonsoft.Json; using PuppeteerSharp.PageAccessibility; using PuppeteerSharp.Tests.Attributes; using PuppeteerSharp.Xunit; @@ -97,6 +95,7 @@ await Page.SetContentAsync(@" Role= "combobox", Name= "", Value= "First Option", + HasPopup = "menu", Children= new SerializedAXNode[]{ new SerializedAXNode { @@ -162,7 +161,8 @@ public async Task RoleDescription() { await Page.SetContentAsync("
Hi
"); var snapshot = await Page.Accessibility.SnapshotAsync(); - Assert.Equal("foo", snapshot.Children[0].RoleDescription); + // See https://chromium-review.googlesource.com/c/chromium/src/+/3088862 + Assert.Null(snapshot.Children[0].RoleDescription); } [PuppeteerTest("accessibility.spec.ts", "Accessibility", "orientation")] diff --git a/lib/PuppeteerSharp.Tests/AccessibilityTests/RootOptionTests.cs b/lib/PuppeteerSharp.Tests/AccessibilityTests/RootOptionTests.cs index 5b1dfd132..9a279a4a8 100644 --- a/lib/PuppeteerSharp.Tests/AccessibilityTests/RootOptionTests.cs +++ b/lib/PuppeteerSharp.Tests/AccessibilityTests/RootOptionTests.cs @@ -60,12 +60,13 @@ await Page.SetContentAsync(@" "); var menu = await Page.QuerySelectorAsync("div[role=\"menu\"]"); - Assert.Equal( - new SerializedAXNode - { - Role = "menu", - Name = "My Menu", - Children = new[] + var snapshot = await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions { Root = menu }); + var nodeToCheck = new SerializedAXNode + { + Role = "menu", + Name = "My Menu", + Orientation = "vertical", + Children = new[] { new SerializedAXNode { @@ -83,8 +84,9 @@ await Page.SetContentAsync(@" Name = "Third Item" } } - }, - await Page.Accessibility.SnapshotAsync(new AccessibilitySnapshotOptions { Root = menu })); + }; + + Assert.Equal(nodeToCheck, snapshot); } [PuppeteerTest("accessibility.spec.ts", "root option", "should return null when the element is no longer in DOM")] diff --git a/lib/PuppeteerSharp.Tests/Assets/simple-extension/content-script.js b/lib/PuppeteerSharp.Tests/Assets/simple-extension/content-script.js deleted file mode 100644 index 0fd83b90f..000000000 --- a/lib/PuppeteerSharp.Tests/Assets/simple-extension/content-script.js +++ /dev/null @@ -1,2 +0,0 @@ -console.log('hey from the content-script'); -self.thisIsTheContentScript = true; diff --git a/lib/PuppeteerSharp.Tests/Assets/simple-extension/index.js b/lib/PuppeteerSharp.Tests/Assets/simple-extension/index.js deleted file mode 100644 index a0bb3f4ea..000000000 --- a/lib/PuppeteerSharp.Tests/Assets/simple-extension/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// Mock script for background extension -window.MAGIC = 42; diff --git a/lib/PuppeteerSharp.Tests/Assets/simple-extension/manifest.json b/lib/PuppeteerSharp.Tests/Assets/simple-extension/manifest.json deleted file mode 100644 index da2cd082e..000000000 --- a/lib/PuppeteerSharp.Tests/Assets/simple-extension/manifest.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "Simple extension", - "version": "0.1", - "background": { - "scripts": ["index.js"] - }, - "content_scripts": [{ - "matches": [""], - "css": [], - "js": ["content-script.js"] - }], - "permissions": ["background", "activeTab"], - "manifest_version": 2 -} diff --git a/lib/PuppeteerSharp.Tests/Attributes/SkipBrowserFact.cs b/lib/PuppeteerSharp.Tests/Attributes/SkipBrowserFact.cs deleted file mode 100644 index 299e9ecaf..000000000 --- a/lib/PuppeteerSharp.Tests/Attributes/SkipBrowserFact.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Xunit; - -namespace PuppeteerSharp.Tests.Attributes -{ - /// - /// Skip browsers. - /// - public class SkipBrowserFact : PuppeteerFact - { - /// - /// Creates a new - /// - /// Skip firefox - /// Skip Chromium - public SkipBrowserFact( - bool skipFirefox = false, - bool skipChromium = false) - { - if (SkipBrowser(skipFirefox, skipChromium)) - { - Skip = "Skipped by browser"; - } - } - - private static bool SkipBrowser(bool skipFirefox, bool skipChromium) - => (skipFirefox && !TestConstants.IsChrome) || (skipChromium && TestConstants.IsChrome); - } -} diff --git a/lib/PuppeteerSharp.Tests/BrowserContextTests/BrowserContextTests.cs b/lib/PuppeteerSharp.Tests/BrowserContextTests/BrowserContextTests.cs deleted file mode 100644 index ded3eb0b9..000000000 --- a/lib/PuppeteerSharp.Tests/BrowserContextTests/BrowserContextTests.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.BrowserContextTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class BrowserContextTests : PuppeteerBrowserBaseTest - { - public BrowserContextTests(ITestOutputHelper output) : base(output) - { - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should have default context")] - [PuppeteerFact] - public async Task ShouldHaveDefaultContext() - { - Assert.Single(Browser.BrowserContexts()); - var defaultContext = Browser.BrowserContexts()[0]; - Assert.False(defaultContext.IsIncognito); - var exception = await Assert.ThrowsAsync(defaultContext.CloseAsync); - Assert.Same(defaultContext, Browser.DefaultContext); - Assert.Contains("cannot be closed", exception.Message); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should create new incognito context")] - [PuppeteerFact] - public async Task ShouldCreateNewIncognitoContext() - { - Assert.Single(Browser.BrowserContexts()); - var context = await Browser.CreateIncognitoBrowserContextAsync(); - Assert.True(context.IsIncognito); - Assert.Equal(2, Browser.BrowserContexts().Length); - Assert.Contains(context, Browser.BrowserContexts()); - await context.CloseAsync(); - Assert.Single(Browser.BrowserContexts()); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should close all belonging targets once closing context")] - [PuppeteerFact] - public async Task ShouldCloseAllBelongingTargetsOnceClosingContext() - { - Assert.Single((await Browser.PagesAsync())); - - var context = await Browser.CreateIncognitoBrowserContextAsync(); - await context.NewPageAsync(); - Assert.Equal(2, (await Browser.PagesAsync()).Length); - Assert.Single((await context.PagesAsync())); - await context.CloseAsync(); - Assert.Single((await Browser.PagesAsync())); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "window.open should use parent tab context")] - [SkipBrowserFact(skipFirefox: true)] - public async Task WindowOpenShouldUseParentTabContext() - { - var context = await Browser.CreateIncognitoBrowserContextAsync(); - var page = await context.NewPageAsync(); - await page.GoToAsync(TestConstants.EmptyPage); - var popupTargetCompletion = new TaskCompletionSource(); - Browser.TargetCreated += (_, e) => popupTargetCompletion.SetResult(e.Target); - - await Task.WhenAll( - popupTargetCompletion.Task, - page.EvaluateFunctionAsync("url => window.open(url)", TestConstants.EmptyPage) - ); - - var popupTarget = await popupTargetCompletion.Task; - Assert.Same(context, popupTarget.BrowserContext); - await context.CloseAsync(); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should fire target events")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldFireTargetEvents() - { - var context = await Browser.CreateIncognitoBrowserContextAsync(); - var events = new List(); - context.TargetCreated += (_, e) => events.Add("CREATED: " + e.Target.Url); - context.TargetChanged += (_, e) => events.Add("CHANGED: " + e.Target.Url); - context.TargetDestroyed += (_, e) => events.Add("DESTROYED: " + e.Target.Url); - var page = await context.NewPageAsync(); - await page.GoToAsync(TestConstants.EmptyPage); - await page.CloseAsync(); - Assert.Equal(new[] { - $"CREATED: {TestConstants.AboutBlank}", - $"CHANGED: {TestConstants.EmptyPage}", - $"DESTROYED: {TestConstants.EmptyPage}" - }, events); - await context.CloseAsync(); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should isolate local storage and cookies")] - [PuppeteerFact] - public async Task ShouldIsolateLocalStorageAndCookies() - { - // Create two incognito contexts. - var context1 = await Browser.CreateIncognitoBrowserContextAsync(); - var context2 = await Browser.CreateIncognitoBrowserContextAsync(); - Assert.Empty(context1.Targets()); - Assert.Empty(context2.Targets()); - - // Create a page in first incognito context. - var page1 = await context1.NewPageAsync(); - await page1.GoToAsync(TestConstants.EmptyPage); - await page1.EvaluateExpressionAsync(@"{ - localStorage.setItem('name', 'page1'); - document.cookie = 'name=page1'; - }"); - - Assert.Single(context1.Targets()); - Assert.Empty(context2.Targets()); - - // Create a page in second incognito context. - var page2 = await context2.NewPageAsync(); - await page2.GoToAsync(TestConstants.EmptyPage); - await page2.EvaluateExpressionAsync(@"{ - localStorage.setItem('name', 'page2'); - document.cookie = 'name=page2'; - }"); - - Assert.Single(context1.Targets()); - Assert.Equal(page1.Target, context1.Targets()[0]); - Assert.Single(context2.Targets()); - Assert.Equal(page2.Target, context2.Targets()[0]); - - // Make sure pages don't share localstorage or cookies. - Assert.Equal("page1", await page1.EvaluateExpressionAsync("localStorage.getItem('name')")); - Assert.Equal("name=page1", await page1.EvaluateExpressionAsync("document.cookie")); - Assert.Equal("page2", await page2.EvaluateExpressionAsync("localStorage.getItem('name')")); - Assert.Equal("name=page2", await page2.EvaluateExpressionAsync("document.cookie")); - - // Cleanup contexts. - await Task.WhenAll(context1.CloseAsync(), context2.CloseAsync()); - Assert.Single(Browser.BrowserContexts()); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should work across sessions")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldWorkAcrossSessions() - { - Assert.Single(Browser.BrowserContexts()); - var context = await Browser.CreateIncognitoBrowserContextAsync(); - Assert.Equal(2, Browser.BrowserContexts().Length); - - var remoteBrowser = await Puppeteer.ConnectAsync(new ConnectOptions - { - BrowserWSEndpoint = Browser.WebSocketEndpoint - }); - var contexts = remoteBrowser.BrowserContexts(); - Assert.Equal(2, contexts.Length); - remoteBrowser.Disconnect(); - await context.CloseAsync(); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should wait for target")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldWaitForTarget() - { - var context = await Browser.CreateIncognitoBrowserContextAsync(); - var targetPromise = context.WaitForTargetAsync((target) => target.Url == TestConstants.EmptyPage); - var page = await context.NewPageAsync(); - await page.GoToAsync(TestConstants.EmptyPage); - var promiseTarget = await targetPromise; - var targetPage = await promiseTarget.PageAsync(); - Assert.Equal(targetPage, page); - await context.CloseAsync(); - } - - [PuppeteerTest("browsercontext.spec.ts", "BrowserContext", "should timeout waiting for non existant target")] - [PuppeteerFact] - public async Task ShouldTimeoutWaitingForNonExistantTarget() - { - var context = await Browser.CreateIncognitoBrowserContextAsync(); - await Assert.ThrowsAsync(() - => context.WaitForTargetAsync((target) => target.Url == TestConstants.EmptyPage, new WaitForOptions { Timeout = 1 })); - await context.CloseAsync(); - } - } -} diff --git a/lib/PuppeteerSharp.Tests/BrowserContextTests/DefaultBrowserContextTests.cs b/lib/PuppeteerSharp.Tests/BrowserContextTests/DefaultBrowserContextTests.cs deleted file mode 100644 index 61f482483..000000000 --- a/lib/PuppeteerSharp.Tests/BrowserContextTests/DefaultBrowserContextTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.BrowserContextTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class DefaultBrowserContextTests : PuppeteerPageBaseTest - { - public DefaultBrowserContextTests(ITestOutputHelper output) : base(output) - { - } - - public override async Task InitializeAsync() - { - await base.InitializeAsync(); - - Context = Browser.DefaultContext; - Page = await Context.NewPageAsync(); - } - - [PuppeteerTest("defaultbrowsercontext.spec.ts", "DefaultBrowserContext", "page.cookies() should work")] - [PuppeteerFact] - public async Task PageGetCookiesAsyncShouldWork() - { - await Page.GoToAsync(TestConstants.EmptyPage); - - await Page.EvaluateExpressionAsync("document.cookie = 'username=John Doe'"); - var cookie = (await Page.GetCookiesAsync()).First(); - Assert.Equal("username", cookie.Name); - Assert.Equal("John Doe", cookie.Value); - Assert.Equal("localhost", cookie.Domain); - Assert.Equal("/", cookie.Path); - Assert.Equal(-1, cookie.Expires); - Assert.Equal(16, cookie.Size); - Assert.False(cookie.HttpOnly); - Assert.False(cookie.Secure); - Assert.True(cookie.Session); - } - - [PuppeteerTest("defaultbrowsercontext.spec.ts", "DefaultBrowserContext", "page.setCookie() should work")] - [PuppeteerFact] - public async Task PageSetCookiesAsyncShouldWork() - { - await Page.GoToAsync(TestConstants.EmptyPage); - - await Page.SetCookieAsync(new CookieParam - { - Name = "username", - Value = "John Doe" - }); - - var cookie = (await Page.GetCookiesAsync()).First(); - Assert.Equal("username", cookie.Name); - Assert.Equal("John Doe", cookie.Value); - Assert.Equal("localhost", cookie.Domain); - Assert.Equal("/", cookie.Path); - Assert.Equal(-1, cookie.Expires); - Assert.Equal(16, cookie.Size); - Assert.False(cookie.HttpOnly); - Assert.False(cookie.Secure); - Assert.True(cookie.Session); - } - - [PuppeteerTest("defaultbrowsercontext.spec.ts", "DefaultBrowserContext", "page.deleteCookie() should work")] - [PuppeteerFact] - public async Task PageDeleteCookieAsyncShouldWork() - { - await Page.GoToAsync(TestConstants.EmptyPage); - - await Page.SetCookieAsync( - new CookieParam - { - Name = "cookie1", - Value = "1" - }, - new CookieParam - { - Name = "cookie2", - Value = "2" - }); - - Assert.Equal("cookie1=1; cookie2=2", await Page.EvaluateExpressionAsync("document.cookie")); - await Page.DeleteCookieAsync(new CookieParam - { - Name = "cookie2" - }); - Assert.Equal("cookie1=1", await Page.EvaluateExpressionAsync("document.cookie")); - - var cookie = (await Page.GetCookiesAsync()).First(); - Assert.Equal("cookie1", cookie.Name); - Assert.Equal("1", cookie.Value); - Assert.Equal("localhost", cookie.Domain); - Assert.Equal("/", cookie.Path); - Assert.Equal(-1, cookie.Expires); - Assert.Equal(8, cookie.Size); - Assert.False(cookie.HttpOnly); - Assert.False(cookie.Secure); - Assert.True(cookie.Session); - } - } -} diff --git a/lib/PuppeteerSharp.Tests/BrowserTests/IsConnectedTests.cs b/lib/PuppeteerSharp.Tests/BrowserTests/IsConnectedTests.cs deleted file mode 100644 index 04511fc81..000000000 --- a/lib/PuppeteerSharp.Tests/BrowserTests/IsConnectedTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Threading.Tasks; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.BrowserTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class IsConnectedTests : PuppeteerBrowserBaseTest - { - public IsConnectedTests(ITestOutputHelper output) : base(output) - { - } - - [PuppeteerTest("browser.spec.ts", "Browser.isConnected", "should set the browser connected state")] - [PuppeteerFact] - public async Task ShouldSetTheBrowserConnectedState() - { - var newBrowser = await Puppeteer.ConnectAsync(new ConnectOptions - { - BrowserWSEndpoint = Browser.WebSocketEndpoint - }); - Assert.True(newBrowser.IsConnected); - newBrowser.Disconnect(); - Assert.False(newBrowser.IsConnected); - } - } -} \ No newline at end of file diff --git a/lib/PuppeteerSharp.Tests/BrowserTests/ProcessTests.cs b/lib/PuppeteerSharp.Tests/BrowserTests/ProcessTests.cs deleted file mode 100644 index 011579914..000000000 --- a/lib/PuppeteerSharp.Tests/BrowserTests/ProcessTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Threading.Tasks; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.BrowserTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class ProcessTests : PuppeteerBrowserBaseTest - { - public ProcessTests(ITestOutputHelper output) : base(output) { } - - [PuppeteerTest("browser.spec.ts", "Browser.process", "should return child_process instance")] - [PuppeteerFact] - public async Task ShouldReturnProcessInstance() - { - var process = Browser.Process; - Assert.True(process.Id > 0); - var browserWSEndpoint = Browser.WebSocketEndpoint; - var remoteBrowser = await Puppeteer.ConnectAsync( - new ConnectOptions { BrowserWSEndpoint = browserWSEndpoint }, TestConstants.LoggerFactory); - Assert.Null(remoteBrowser.Process); - remoteBrowser.Disconnect(); - } - } -} \ No newline at end of file diff --git a/lib/PuppeteerSharp.Tests/BrowserTests/TargetTests.cs b/lib/PuppeteerSharp.Tests/BrowserTests/TargetTests.cs deleted file mode 100644 index 5a6609f19..000000000 --- a/lib/PuppeteerSharp.Tests/BrowserTests/TargetTests.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.BrowserTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class TargetTests : PuppeteerBrowserBaseTest - { - public TargetTests(ITestOutputHelper output) : base(output) - { - } - - [PuppeteerTest("browser.spec.ts", "Browser.target", "should return browser target")] - [PuppeteerFact] - public void ShouldReturnBrowserTarget() - => Assert.Equal(TargetType.Browser, Browser.Target.Type); - } -} \ No newline at end of file diff --git a/lib/PuppeteerSharp.Tests/BrowserTests/UserAgentTests.cs b/lib/PuppeteerSharp.Tests/BrowserTests/UserAgentTests.cs deleted file mode 100644 index a0c95a12e..000000000 --- a/lib/PuppeteerSharp.Tests/BrowserTests/UserAgentTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Threading.Tasks; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.BrowserTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class UserAgentTests : PuppeteerBrowserBaseTest - { - public UserAgentTests(ITestOutputHelper output) : base(output) - { - } - - [PuppeteerTest("browser.spec.ts", "Browser.userAgent", "should include WebKit")] - [PuppeteerFact] - public async Task ShouldIncludeWebKit() - { - var userAgent = await Browser.GetUserAgentAsync(); - Assert.NotEmpty(userAgent); - - if (TestConstants.IsChrome) - { - Assert.Contains("WebKit", userAgent); - } - else - { - Assert.Contains("Gecko", userAgent); - } - } - } -} \ No newline at end of file diff --git a/lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs b/lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs deleted file mode 100644 index 4685bbc3e..000000000 --- a/lib/PuppeteerSharp.Tests/CDPSessionTests/CreateCDPSessionTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using PuppeteerSharp.Messaging; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.CDPSessionTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class CreateCDPSessionTests : PuppeteerPageBaseTest - { - public CreateCDPSessionTests(ITestOutputHelper output) : base(output) - { - } - - [PuppeteerTest("CDPSession.spec.ts", "Target.createCDPSession", "should work")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldWork() - { - var client = await Page.Target.CreateCDPSessionAsync(); - - await Task.WhenAll( - client.SendAsync("Runtime.enable"), - client.SendAsync("Runtime.evaluate", new RuntimeEvaluateRequest { Expression = "window.foo = 'bar'" }) - ); - var foo = await Page.EvaluateExpressionAsync("window.foo"); - Assert.Equal("bar", foo); - } - - [PuppeteerTest("CDPSession.spec.ts", "Target.createCDPSession", "should send events")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldSendEvents() - { - var client = await Page.Target.CreateCDPSessionAsync(); - await client.SendAsync("Network.enable"); - var events = new List(); - - client.MessageReceived += (_, e) => - { - if (e.MessageID == "Network.requestWillBeSent") - { - events.Add(e.MessageData); - } - }; - - await Page.GoToAsync(TestConstants.EmptyPage); - Assert.Single(events); - } - - [PuppeteerTest("CDPSession.spec.ts", "Target.createCDPSession", "should enable and disable domains independently")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldEnableAndDisableDomainsIndependently() - { - var client = await Page.Target.CreateCDPSessionAsync(); - await client.SendAsync("Runtime.enable"); - await client.SendAsync("Debugger.enable"); - // JS coverage enables and then disables Debugger domain. - await Page.Coverage.StartJSCoverageAsync(); - await Page.Coverage.StopJSCoverageAsync(); - // generate a script in page and wait for the event. - var eventTask = WaitEvent(client, "Debugger.scriptParsed"); - await Task.WhenAll( - eventTask, - Page.EvaluateExpressionAsync("//# sourceURL=foo.js") - ); - // expect events to be dispatched. - Assert.Equal("foo.js", eventTask.Result["url"].Value()); - } - - [PuppeteerTest("CDPSession.spec.ts", "Target.createCDPSession", "should be able to detach session")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldBeAbleToDetachSession() - { - var client = await Page.Target.CreateCDPSessionAsync(); - await client.SendAsync("Runtime.enable"); - var evalResponse = await client.SendAsync("Runtime.evaluate", new RuntimeEvaluateRequest - { - Expression = "1 + 2", - ReturnByValue = true - }); - Assert.Equal(3, evalResponse["result"]["value"].ToObject()); - await client.DetachAsync(); - - var exception = await Assert.ThrowsAnyAsync(() - => client.SendAsync("Runtime.evaluate", new RuntimeEvaluateRequest - { - Expression = "3 + 1", - ReturnByValue = true - })); - Assert.Contains("Session closed.", exception.Message); - } - - [PuppeteerTest("CDPSession.spec.ts", "Target.createCDPSession", "should throw nice errors")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldThrowNiceErrors() - { - var client = await Page.Target.CreateCDPSessionAsync(); - async Task TheSourceOfTheProblems() => await client.SendAsync("ThisCommand.DoesNotExist"); - - var exception = await Assert.ThrowsAsync(async () => - { - await TheSourceOfTheProblems(); - }); - Assert.Contains("TheSourceOfTheProblems", exception.StackTrace); - Assert.Contains("ThisCommand.DoesNotExist", exception.Message); - } - - [PuppeteerTest("CDPSession.spec.ts", "Target.createCDPSession", "should expose the underlying connection")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldExposeTheUnderlyingConnection() - => Assert.NotNull(await Page.Target.CreateCDPSessionAsync()); - } -} diff --git a/lib/PuppeteerSharp.Tests/CefSharp.Dom.Tests.csproj b/lib/PuppeteerSharp.Tests/CefSharp.Dom.Tests.csproj new file mode 100644 index 000000000..cdf6465b8 --- /dev/null +++ b/lib/PuppeteerSharp.Tests/CefSharp.Dom.Tests.csproj @@ -0,0 +1,93 @@ + + + + netcoreapp3.1 + win-x64 + false + true + false + false + CefSharp.Dom.Tests + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + PreserveNewest + + + + + + + + + + + + diff --git a/lib/PuppeteerSharp.Tests/ChromiumOnlyTests/BrowserUrlOptionTests.cs b/lib/PuppeteerSharp.Tests/ChromiumOnlyTests/BrowserUrlOptionTests.cs deleted file mode 100644 index b5b71f857..000000000 --- a/lib/PuppeteerSharp.Tests/ChromiumOnlyTests/BrowserUrlOptionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Threading.Tasks; -using PuppeteerSharp.Tests.Attributes; -using PuppeteerSharp.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace PuppeteerSharp.Tests.ChromiumSpecificTests -{ - [Collection(TestConstants.TestFixtureCollectionName)] - public class BrowserUrlOptionTests : PuppeteerPageBaseTest - { - public BrowserUrlOptionTests(ITestOutputHelper output) : base(output) - { - } - - [PuppeteerTest("chromiumonly.spec.ts", "Puppeteer.launch |browserURL| option", "should be able to connect using browserUrl, with and without trailing slash")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldBeAbleToConnectUsingBrowserURLWithAndWithoutTrailingSlash() - { - var options = TestConstants.DefaultBrowserOptions(); - options.Args = new string[] { "--remote-debugging-port=21222" }; - var originalBrowser = await Puppeteer.LaunchAsync(options); - var browserURL = "http://127.0.0.1:21222"; - - var browser1 = await Puppeteer.ConnectAsync(new ConnectOptions { BrowserURL = browserURL }); - var page1 = await browser1.NewPageAsync(); - Assert.Equal(56, await page1.EvaluateExpressionAsync("7 * 8")); - browser1.Disconnect(); - - var browser2 = await Puppeteer.ConnectAsync(new ConnectOptions { BrowserURL = browserURL + "/" }); - var page2 = await browser2.NewPageAsync(); - Assert.Equal(56, await page2.EvaluateExpressionAsync("7 * 8")); - browser2.Disconnect(); - await originalBrowser.CloseAsync(); - } - - [PuppeteerTest("chromiumonly.spec.ts", "Puppeteer.launch |browserURL| option", "should throw when using both browserWSEndpoint and browserURL")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldThrowWhenUsingBothBrowserWSEndpointAndBrowserURL() - { - var options = TestConstants.DefaultBrowserOptions(); - options.Args = new string[] { "--remote-debugging-port=21222" }; - var originalBrowser = await Puppeteer.LaunchAsync(options); - var browserURL = "http://127.0.0.1:21222"; - - await Assert.ThrowsAsync(() => Puppeteer.ConnectAsync(new ConnectOptions - { - BrowserURL = browserURL, - BrowserWSEndpoint = originalBrowser.WebSocketEndpoint - })); - - await originalBrowser.CloseAsync(); - } - - [PuppeteerTest("chromiumonly.spec.ts", "Puppeteer.launch |browserURL| option", "should throw when trying to connect to non-existing browser")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldThrowWhenTryingToConnectToNonExistingBrowser() - { - var options = TestConstants.DefaultBrowserOptions(); - options.Args = new string[] { "--remote-debugging-port=21222" }; - var originalBrowser = await Puppeteer.LaunchAsync(options); - var browserURL = "http://127.0.0.1:2122"; - - await Assert.ThrowsAsync(() => Puppeteer.ConnectAsync(new ConnectOptions - { - BrowserURL = browserURL - })); - - await originalBrowser.CloseAsync(); - } - } -} diff --git a/lib/PuppeteerSharp.Tests/ClickTests/ClickTests.cs b/lib/PuppeteerSharp.Tests/ClickTests/ClickTests.cs index 98d9c7dac..5b3d171ed 100644 --- a/lib/PuppeteerSharp.Tests/ClickTests/ClickTests.cs +++ b/lib/PuppeteerSharp.Tests/ClickTests/ClickTests.cs @@ -1,7 +1,7 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; -using PuppeteerSharp.Input; +using CefSharp.Dom; +using CefSharp.Dom.Input; using PuppeteerSharp.Tests.Attributes; using PuppeteerSharp.Xunit; using Xunit; @@ -10,7 +10,7 @@ namespace PuppeteerSharp.Tests.ClickTests { [Collection(TestConstants.TestFixtureCollectionName)] - public class ClickTests : PuppeteerPageBaseTest + public class ClickTests : DevToolsContextBaseTest { public ClickTests(ITestOutputHelper output) : base(output) { @@ -20,39 +20,39 @@ public ClickTests(ITestOutputHelper output) : base(output) [PuppeteerFact] public async Task ShouldClickTheButton() { - await Page.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); - await Page.ClickAsync("button"); - Assert.Equal("Clicked", await Page.EvaluateExpressionAsync("result")); + await DevToolsContext.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); + await DevToolsContext.ClickAsync("button"); + Assert.Equal("Clicked", await DevToolsContext.EvaluateExpressionAsync("result")); } [PuppeteerTest("click.spec.ts", "Page.click", "should click svg")] [PuppeteerFact] public async Task ShouldClickSvg() { - await Page.SetContentAsync($@" + await DevToolsContext.SetContentAsync($@" "); - await Page.ClickAsync("circle"); - Assert.Equal(42, await Page.EvaluateFunctionAsync("() => window.__CLICKED")); + await DevToolsContext.ClickAsync("circle"); + Assert.Equal(42, await DevToolsContext.EvaluateFunctionAsync("() => window.__CLICKED")); } [PuppeteerTest("click.spec.ts", "Page.click", "should click the button if window.Node is removed")] - [SkipBrowserFact(skipFirefox: true)] + [PuppeteerFact] public async Task ShouldClickTheButtonIfWindowNodeIsRemoved() { - await Page.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); - await Page.EvaluateExpressionAsync("delete window.Node"); - await Page.ClickAsync("button"); - Assert.Equal("Clicked", await Page.EvaluateExpressionAsync("result")); + await DevToolsContext.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); + await DevToolsContext.EvaluateExpressionAsync("delete window.Node"); + await DevToolsContext.ClickAsync("button"); + Assert.Equal("Clicked", await DevToolsContext.EvaluateExpressionAsync("result")); } [PuppeteerTest("click.spec.ts", "Page.click", "should click on a span with an inline element inside")] [PuppeteerFact(Skip = "See https://github.com/GoogleChrome/puppeteer/issues/4281")] public async Task ShouldClickOnASpanWithAnInlineElementInside() { - await Page.SetContentAsync($@" + await DevToolsContext.SetContentAsync($@" "); - await Page.ClickAsync("span"); - Assert.Equal(42, await Page.EvaluateFunctionAsync("() => window.CLICKED")); - } - - /// - /// This test is called ShouldNotThrowUnhandledPromiseRejectionWhenPageCloses in puppeteer. - /// - [PuppeteerTest("click.spec.ts", "Page.click", "should not throw UnhandledPromiseRejection when page closes")] - [PuppeteerFact(Skip = "We don't need this test")] - public async Task ShouldGracefullyFailWhenPageCloses() - { - var newPage = await Browser.NewPageAsync(); - await Task.WhenAll( - newPage.CloseAsync(), - newPage.Mouse.ClickAsync(1, 2)); + await DevToolsContext.ClickAsync("span"); + Assert.Equal(42, await DevToolsContext.EvaluateFunctionAsync("() => window.CLICKED")); } - [PuppeteerTest("click.spec.ts", "Page.click", "should click the button after navigation ")] [PuppeteerFact] public async Task ShouldClickTheButtonAfterNavigation() { - await Page.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); - await Page.ClickAsync("button"); - await Page.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); - await Page.ClickAsync("button"); - Assert.Equal("Clicked", await Page.EvaluateExpressionAsync("result")); - } - - [PuppeteerTest("click.spec.ts", "Page.click", "should click with disabled javascript")] - [SkipBrowserFact(skipFirefox: true)] - public async Task ShouldClickWithDisabledJavascript() - { - await Page.SetJavaScriptEnabledAsync(false); - await Page.GoToAsync(TestConstants.ServerUrl + "/wrappedlink.html"); - await Task.WhenAll( - Page.ClickAsync("a"), - Page.WaitForNavigationAsync() - ); - Assert.Equal(TestConstants.ServerUrl + "/wrappedlink.html#clicked", Page.Url); + await DevToolsContext.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); + await DevToolsContext.ClickAsync("button"); + await DevToolsContext.GoToAsync(TestConstants.ServerUrl + "/input/button.html"); + await DevToolsContext.ClickAsync("button"); + Assert.Equal("Clicked", await DevToolsContext.EvaluateExpressionAsync("result")); } - [PuppeteerTest("click.spec.ts", "Page.click", "should click when one of inline box children is outside of viewport")] [PuppeteerFact] public async Task ShouldClickWhenOneOfInlineBoxChildrenIsOutsideOfViewport() { - await Page.SetContentAsync($@" + await DevToolsContext.SetContentAsync($@"
"); - var elementHandle = await Page.QuerySelectorAsync("div"); + var elementHandle = await DevToolsContext.QuerySelectorAsync("div"); var screenshot = await elementHandle.ScreenshotDataAsync(); Assert.True(ScreenshotHelper.PixelMatch("screenshot-element-padding-border.png", screenshot)); } [PuppeteerTest("screenshot.spec.ts", "ElementHandle.screenshot", "should capture full element when larger than viewport")] - [SkipBrowserFact(skipFirefox: true)] + [PuppeteerFact(Skip = "TODO: CEF")] public async Task ShouldCaptureFullElementWhenLargerThanViewport() { - await Page.SetViewportAsync(new ViewPortOptions + await DevToolsContext.SetViewportAsync(new ViewPortOptions { Width = 500, Height = 500 }); - await Page.SetContentAsync(@" + await DevToolsContext.SetContentAsync(@" something above
" ); - var elementHandle = await Page.QuerySelectorAsync("div.to-screenshot"); + var elementHandle = await DevToolsContext.QuerySelectorAsync("div.to-screenshot"); var screenshot = await elementHandle.ScreenshotDataAsync(); Assert.True(ScreenshotHelper.PixelMatch("screenshot-element-larger-than-viewport.png", screenshot)); Assert.Equal(JToken.FromObject(new { w = 500, h = 500 }), - await Page.EvaluateExpressionAsync("({ w: window.innerWidth, h: window.innerHeight })")); + await DevToolsContext.EvaluateExpressionAsync("({ w: window.innerWidth, h: window.innerHeight })")); } [PuppeteerFact] public async Task ShouldScrollElementIntoView() { - await Page.SetViewportAsync(new ViewPortOptions + await DevToolsContext.SetViewportAsync(new ViewPortOptions { Width = 500, Height = 500 }); - await Page.SetContentAsync(@" + await DevToolsContext.SetContentAsync(@" something above