/CefSharp.OffScreen/ChromiumWebBrowser.cs

https://github.com/cefsharp/CefSharp · C# · 959 lines · 487 code · 116 blank · 356 comment · 70 complexity · e0b90e1229d9aecc201f6d1604f2f7f6 MD5 · raw file

  1. // Copyright © 2014 The CefSharp Authors. All rights reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
  4. using System;
  5. using System.Drawing;
  6. using System.Drawing.Imaging;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using CefSharp.DevTools.Page;
  10. using CefSharp.Enums;
  11. using CefSharp.Internals;
  12. using CefSharp.Structs;
  13. using CefSharp.Web;
  14. using Point = System.Drawing.Point;
  15. using Range = CefSharp.Structs.Range;
  16. using Size = System.Drawing.Size;
  17. namespace CefSharp.OffScreen
  18. {
  19. /// <summary>
  20. /// An offscreen instance of Chromium that you can use to take
  21. /// snapshots or evaluate JavaScript.
  22. /// </summary>
  23. public partial class ChromiumWebBrowser : IRenderWebBrowser
  24. {
  25. /// <summary>
  26. /// The managed cef browser adapter
  27. /// </summary>
  28. private IBrowserAdapter managedCefBrowserAdapter;
  29. /// <summary>
  30. /// Size of the Chromium viewport.
  31. /// This must be set to something other than 0x0 otherwise Chromium will not render,
  32. /// and the ScreenshotAsync task will deadlock.
  33. /// </summary>
  34. private Size size = new Size(1366, 768);
  35. /// <summary>
  36. /// Flag to guard the creation of the underlying offscreen browser - only one instance can be created
  37. /// </summary>
  38. private bool browserCreated;
  39. /// <summary>
  40. /// Action which is called immediately before the <see cref="BrowserInitialized"/> event after the
  41. /// uderlying Chromium Embedded Framework (CEF) browser has been created.
  42. /// </summary>
  43. private Action<IBrowser> onAfterBrowserCreatedDelegate;
  44. private float deviceScaleFactor = 1.0f;
  45. /// <summary>
  46. /// Gets a value indicating whether this instance is disposed.
  47. /// </summary>
  48. /// <value><see langword="true" /> if this instance is disposed; otherwise, <see langword="false" />.</value>
  49. public bool IsDisposed
  50. {
  51. get
  52. {
  53. return Interlocked.CompareExchange(ref disposeSignaled, 1, 1) == 1;
  54. }
  55. }
  56. /// <summary>
  57. /// A flag that indicates whether the WebBrowser is initialized (true) or not (false).
  58. /// </summary>
  59. /// <value><c>true</c> if this instance is browser initialized; otherwise, <c>false</c>.</value>
  60. public bool IsBrowserInitialized
  61. {
  62. get { return InternalIsBrowserInitialized(); }
  63. }
  64. /// <summary>
  65. /// A flag that indicates whether the control is currently loading one or more web pages (true) or not (false).
  66. /// </summary>
  67. /// <value><c>true</c> if this instance is loading; otherwise, <c>false</c>.</value>
  68. /// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
  69. /// binding.</remarks>
  70. public bool IsLoading { get; private set; }
  71. /// <summary>
  72. /// The text that will be displayed as a ToolTip
  73. /// </summary>
  74. /// <value>The tooltip text.</value>
  75. public string TooltipText { get; private set; }
  76. /// <summary>
  77. /// The address (URL) which the browser control is currently displaying.
  78. /// Will automatically be updated as the user navigates to another page (e.g. by clicking on a link).
  79. /// </summary>
  80. /// <value>The address.</value>
  81. /// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
  82. /// binding.</remarks>
  83. public string Address { get; private set; }
  84. /// <summary>
  85. /// A flag that indicates whether the state of the control current supports the GoBack action (true) or not (false).
  86. /// </summary>
  87. /// <value><c>true</c> if this instance can go back; otherwise, <c>false</c>.</value>
  88. /// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
  89. /// binding.</remarks>
  90. public bool CanGoBack { get; private set; }
  91. /// <summary>
  92. /// A flag that indicates whether the state of the control currently supports the GoForward action (true) or not (false).
  93. /// </summary>
  94. /// <value><c>true</c> if this instance can go forward; otherwise, <c>false</c>.</value>
  95. /// <remarks>In the WPF control, this property is implemented as a Dependency Property and fully supports data
  96. /// binding.</remarks>
  97. public bool CanGoForward { get; private set; }
  98. /// <summary>
  99. /// Gets the request context.
  100. /// </summary>
  101. /// <value>The request context.</value>
  102. public IRequestContext RequestContext { get; private set; }
  103. /// <summary>
  104. /// Implement <see cref="IRenderHandler" /> and assign to handle events related to browser rendering.
  105. /// </summary>
  106. /// <value>The render handler.</value>
  107. public IRenderHandler RenderHandler { get; set; }
  108. /// <summary>
  109. /// Implement <see cref="IAccessibilityHandler" /> to handle events related to accessibility.
  110. /// </summary>
  111. /// <value>The accessibility handler.</value>
  112. public IAccessibilityHandler AccessibilityHandler { get; set; }
  113. /// <summary>
  114. /// Event called after the underlying CEF browser instance has been created.
  115. /// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
  116. /// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
  117. /// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
  118. /// (The exception to this is when you're running with settings.MultiThreadedMessageLoop = false, then they'll be the same thread).
  119. /// </summary>
  120. public event EventHandler BrowserInitialized;
  121. /// <summary>
  122. /// Occurs when the browser address changed.
  123. /// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
  124. /// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
  125. /// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
  126. /// (The exception to this is when you're running with settings.MultiThreadedMessageLoop = false, then they'll be the same thread).
  127. /// </summary>
  128. public event EventHandler<AddressChangedEventArgs> AddressChanged;
  129. /// <summary>
  130. /// Occurs when [title changed].
  131. /// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
  132. /// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
  133. /// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
  134. /// (The exception to this is when you're running with settings.MultiThreadedMessageLoop = false, then they'll be the same thread).
  135. /// </summary>
  136. public event EventHandler<TitleChangedEventArgs> TitleChanged;
  137. /// <summary>
  138. /// Fired on the CEF UI thread, which by default is not the same as your application main thread.
  139. /// Called when an element should be painted. Pixel values passed to this method are scaled relative to view coordinates
  140. /// based on the value of ScreenInfo.DeviceScaleFactor returned from GetScreenInfo.
  141. /// </summary>
  142. public event EventHandler<OnPaintEventArgs> Paint;
  143. /// <summary>
  144. /// Used by <see cref="ScreenshotAsync(bool, PopupBlending)"/> as the <see cref="Paint"/>
  145. /// event happens before the bitmap buffer is updated. We do this to allow users to mark <see cref="OnPaintEventArgs.Handled"/>
  146. /// to true and stop the buffer from being updated.
  147. /// </summary>
  148. private event EventHandler<OnPaintEventArgs> AfterPaint;
  149. /// <summary>
  150. /// Create a new OffScreen Chromium Browser. If you use <see cref="CefSharp.JavascriptBinding.JavascriptBindingSettings.LegacyBindingEnabled"/> = true then you must
  151. /// set <paramref name="automaticallyCreateBrowser"/> to false and call <see cref="CreateBrowser"/> after the objects are registered.
  152. /// The underlying Chromium Embedded Framework(CEF) Browser is created asynchronouly, to subscribe to the <see cref="BrowserInitialized"/> event it is recommended
  153. /// that you set <paramref name="automaticallyCreateBrowser"/> to false, subscribe to the event and then call <see cref="CreateBrowser(IWindowInfo, IBrowserSettings)"/>
  154. /// to ensure you are subscribe to the event before it's fired (Issue https://github.com/cefsharp/CefSharp/issues/3552).
  155. /// </summary>
  156. /// <param name="html">html string to be initially loaded in the browser.</param>
  157. /// <param name="browserSettings">The browser settings to use. If null, the default settings are used.</param>
  158. /// <param name="requestContext">See <see cref="RequestContext" /> for more details. Defaults to null</param>
  159. /// <param name="automaticallyCreateBrowser">automatically create the underlying Browser</param>
  160. /// <param name="onAfterBrowserCreated">
  161. /// Use as an alternative to the <see cref="BrowserInitialized"/> event. If the underlying Chromium Embedded Framework (CEF) browser is created successfully,
  162. /// this action is guranteed to be called after the browser created where as the <see cref="BrowserInitialized"/> event may be called before
  163. /// you have a chance to subscribe to the event as the CEF Browser is created async. (Issue https://github.com/cefsharp/CefSharp/issues/3552).
  164. /// </param>
  165. /// <exception cref="System.InvalidOperationException">Cef::Initialize() failed</exception>
  166. public ChromiumWebBrowser(HtmlString html, IBrowserSettings browserSettings = null,
  167. IRequestContext requestContext = null, bool automaticallyCreateBrowser = true,
  168. Action<IBrowser> onAfterBrowserCreated = null) : this(html.ToDataUriString(), browserSettings, requestContext, automaticallyCreateBrowser, onAfterBrowserCreated)
  169. {
  170. }
  171. /// <summary>
  172. /// Create a new OffScreen Chromium Browser. If you use <see cref="CefSharp.JavascriptBinding.JavascriptBindingSettings.LegacyBindingEnabled"/> = true then you must
  173. /// set <paramref name="automaticallyCreateBrowser"/> to false and call <see cref="CreateBrowser"/> after the objects are registered.
  174. /// The underlying Chromium Embedded Framework(CEF) Browser is created asynchronouly, to subscribe to the <see cref="BrowserInitialized"/> event it is recommended
  175. /// that you set <paramref name="automaticallyCreateBrowser"/> to false, subscribe to the event and then call <see cref="CreateBrowser(IWindowInfo, IBrowserSettings)"/>
  176. /// to ensure you are subscribe to the event before it's fired (Issue https://github.com/cefsharp/CefSharp/issues/3552).
  177. /// </summary>
  178. /// <param name="address">Initial address (url) to load</param>
  179. /// <param name="browserSettings">The browser settings to use. If null, the default settings are used.</param>
  180. /// <param name="requestContext">See <see cref="RequestContext" /> for more details. Defaults to null</param>
  181. /// <param name="automaticallyCreateBrowser">automatically create the underlying Browser</param>
  182. /// <param name="onAfterBrowserCreated">
  183. /// Use as an alternative to the <see cref="BrowserInitialized"/> event. If the underlying Chromium Embedded Framework (CEF) browser is created successfully,
  184. /// this action is guranteed to be called after the browser created where as the <see cref="BrowserInitialized"/> event may be called before
  185. /// you have a chance to subscribe to the event as the CEF Browser is created async. (Issue https://github.com/cefsharp/CefSharp/issues/3552).
  186. /// </param>
  187. /// <exception cref="System.InvalidOperationException">Cef::Initialize() failed</exception>
  188. public ChromiumWebBrowser(string address = "", IBrowserSettings browserSettings = null,
  189. IRequestContext requestContext = null, bool automaticallyCreateBrowser = true,
  190. Action<IBrowser> onAfterBrowserCreated = null)
  191. {
  192. if (!Cef.IsInitialized)
  193. {
  194. var settings = new CefSettings();
  195. if (!Cef.Initialize(settings))
  196. {
  197. throw new InvalidOperationException(CefInitializeFailedErrorMessage);
  198. }
  199. }
  200. RequestContext = requestContext;
  201. Cef.AddDisposable(this);
  202. Address = address;
  203. onAfterBrowserCreatedDelegate = onAfterBrowserCreated;
  204. managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, true);
  205. if (automaticallyCreateBrowser)
  206. {
  207. CreateBrowser(null, browserSettings);
  208. }
  209. RenderHandler = new DefaultRenderHandler(this);
  210. }
  211. /// <summary>
  212. /// Finalizes an instance of the <see cref="ChromiumWebBrowser"/> class.
  213. /// </summary>
  214. ~ChromiumWebBrowser()
  215. {
  216. Dispose(false);
  217. }
  218. /// <summary>
  219. /// Releases all resources used by the <see cref="ChromiumWebBrowser"/> object
  220. /// </summary>
  221. public void Dispose()
  222. {
  223. Dispose(true);
  224. GC.SuppressFinalize(this);
  225. }
  226. /// <summary>
  227. /// Releases unmanaged and - optionally - managed resources for the <see cref="ChromiumWebBrowser"/>
  228. /// </summary>
  229. /// <param name="disposing"><see langword="true" /> to release both managed and unmanaged resources; <see langword="false" /> to release only unmanaged resources.</param>
  230. protected virtual void Dispose(bool disposing)
  231. {
  232. // Attempt to move the disposeSignaled state from 0 to 1. If successful, we can be assured that
  233. // this thread is the first thread to do so, and can safely dispose of the object.
  234. if (Interlocked.CompareExchange(ref disposeSignaled, 1, 0) != 0)
  235. {
  236. return;
  237. }
  238. if (disposing)
  239. {
  240. CanExecuteJavascriptInMainFrame = false;
  241. Interlocked.Exchange(ref browserInitialized, 0);
  242. // Don't reference event listeners any longer:
  243. AddressChanged = null;
  244. BrowserInitialized = null;
  245. ConsoleMessage = null;
  246. FrameLoadEnd = null;
  247. FrameLoadStart = null;
  248. LoadError = null;
  249. LoadingStateChanged = null;
  250. Paint = null;
  251. AfterPaint = null;
  252. StatusMessage = null;
  253. TitleChanged = null;
  254. JavascriptMessageReceived = null;
  255. // Release reference to handlers, except LifeSpanHandler which is done after Disposing
  256. // ManagedCefBrowserAdapter otherwise the ILifeSpanHandler.DoClose will not be invoked.
  257. // We also leave FocusHandler and override with a NoFocusHandler implementation as
  258. // it so we can block taking Focus (we're dispoing afterall). Issue #3715
  259. FreeHandlersExceptLifeSpanAndFocus();
  260. FocusHandler = new NoFocusHandler();
  261. browser = null;
  262. BrowserCore = null;
  263. if (managedCefBrowserAdapter != null)
  264. {
  265. managedCefBrowserAdapter.Dispose();
  266. managedCefBrowserAdapter = null;
  267. }
  268. // LifeSpanHandler is set to null after managedCefBrowserAdapter.Dispose so ILifeSpanHandler.DoClose
  269. // is called.
  270. LifeSpanHandler = null;
  271. }
  272. Cef.RemoveDisposable(this);
  273. }
  274. /// <summary>
  275. /// Create the underlying browser. The instance address, browser settings and request context will be used.
  276. /// </summary>
  277. /// <param name="windowInfo">Window information used when creating the browser</param>
  278. /// <param name="browserSettings">Browser initialization settings</param>
  279. /// <exception cref="System.Exception">An instance of the underlying offscreen browser has already been created, this method can only be called once.</exception>
  280. public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browserSettings = null)
  281. {
  282. if (browserCreated)
  283. {
  284. throw new Exception("An instance of the underlying offscreen browser has already been created, this method can only be called once.");
  285. }
  286. browserCreated = true;
  287. if (browserSettings == null)
  288. {
  289. browserSettings = Core.ObjectFactory.CreateBrowserSettings(autoDispose: true);
  290. }
  291. if (windowInfo == null)
  292. {
  293. windowInfo = Core.ObjectFactory.CreateWindowInfo();
  294. windowInfo.SetAsWindowless(IntPtr.Zero);
  295. }
  296. //TODO: We need some sort of timeout and
  297. //if we use the same approach for WPF/WinForms then
  298. //we need to move the common code into the partial class
  299. GlobalContextInitialized.ExecuteOrEnqueue((success) =>
  300. {
  301. if(!success)
  302. {
  303. return;
  304. }
  305. managedCefBrowserAdapter.CreateBrowser(windowInfo, browserSettings, RequestContext, Address);
  306. //Dispose of BrowserSettings if we created it, if user created then they're responsible
  307. if (browserSettings.AutoDispose)
  308. {
  309. browserSettings.Dispose();
  310. }
  311. browserSettings = null;
  312. });
  313. }
  314. /// <summary>
  315. /// Create the underlying CEF browser. The address and request context passed into the constructor
  316. /// will be used. If a <see cref="Action{IBrowser}"/> delegate was passed
  317. /// into the constructor it will not be called as this method overrides that value internally.
  318. /// </summary>
  319. /// <param name="windowInfo">Window information used when creating the browser</param>
  320. /// <param name="browserSettings">Browser initialization settings</param>
  321. /// <exception cref="System.Exception">An instance of the underlying offscreen browser has already been created, this method can only be called once.</exception>
  322. /// <returns>
  323. /// A <see cref="Task{IBrowser}"/> that represents the creation of the underlying CEF browser (<see cref="IBrowser"/> instance.
  324. /// When the task completes then the CEF Browser will have been created and you can start performing basic tasks.
  325. /// Note that the control's <see cref="BrowserInitialized"/> event will be invoked after this task completes.
  326. /// </returns>
  327. public Task<IBrowser> CreateBrowserAsync(IWindowInfo windowInfo = null, IBrowserSettings browserSettings = null)
  328. {
  329. var tcs = new TaskCompletionSource<IBrowser>();
  330. onAfterBrowserCreatedDelegate += new Action<IBrowser>(b =>
  331. {
  332. tcs.TrySetResultAsync(b);
  333. });
  334. try
  335. {
  336. CreateBrowser(windowInfo, browserSettings);
  337. }
  338. catch(Exception ex)
  339. {
  340. tcs.TrySetExceptionAsync(ex);
  341. }
  342. return tcs.Task;
  343. }
  344. /// <summary>
  345. /// Get/set the size of the Chromium viewport, in pixels.
  346. /// This also changes the size of the next rendered bitmap.
  347. /// </summary>
  348. /// <value>The size.</value>
  349. public Size Size
  350. {
  351. get { return size; }
  352. set
  353. {
  354. if (size != value)
  355. {
  356. size = value;
  357. if (IsBrowserInitialized)
  358. {
  359. browser.GetHost().WasResized();
  360. }
  361. }
  362. }
  363. }
  364. /// <summary>
  365. /// Device scale factor. Specifies the ratio between physical and logical pixels.
  366. /// </summary>
  367. public float DeviceScaleFactor
  368. {
  369. get { return deviceScaleFactor; }
  370. set
  371. {
  372. deviceScaleFactor = value;
  373. if (IsBrowserInitialized)
  374. {
  375. browser.GetHost().NotifyScreenInfoChanged();
  376. }
  377. }
  378. }
  379. /// <summary>
  380. /// Immediately returns a copy of the last rendering from Chrome,
  381. /// or null if no rendering has occurred yet.
  382. /// Chrome also renders the page loading, so if you want to see a complete rendering,
  383. /// only start this task once your page is loaded (which you can detect via FrameLoadEnd
  384. /// or your own heuristics based on evaluating JavaScript).
  385. /// It is your responsibility to dispose the returned Bitmap.
  386. /// The bitmap size is determined by the Size property set earlier.
  387. /// </summary>
  388. /// <param name="blend">Choose which bitmap to retrieve, choose <see cref="PopupBlending.Blend"/> for a merged bitmap.</param>
  389. /// <returns>Bitmap.</returns>
  390. public Bitmap ScreenshotOrNull(PopupBlending blend = PopupBlending.Main)
  391. {
  392. if (RenderHandler == null)
  393. {
  394. throw new NullReferenceException("RenderHandler cannot be null. Use DefaultRenderHandler unless implementing your own");
  395. }
  396. var renderHandler = RenderHandler as DefaultRenderHandler;
  397. if (renderHandler == null)
  398. {
  399. throw new Exception("ScreenshotOrNull and ScreenshotAsync can only be used in combination with the DefaultRenderHandler");
  400. }
  401. lock (renderHandler.BitmapLock)
  402. {
  403. if (blend == PopupBlending.Main)
  404. {
  405. return renderHandler.BitmapBuffer.CreateBitmap();
  406. }
  407. if (blend == PopupBlending.Popup)
  408. {
  409. return renderHandler.PopupOpen ? renderHandler.PopupBuffer.CreateBitmap() : null;
  410. }
  411. var bitmap = renderHandler.BitmapBuffer.CreateBitmap();
  412. if (renderHandler.PopupOpen && bitmap != null)
  413. {
  414. var popup = renderHandler.PopupBuffer.CreateBitmap();
  415. if (popup == null)
  416. {
  417. return bitmap;
  418. }
  419. return MergeBitmaps(bitmap, popup, renderHandler.PopupPosition);
  420. }
  421. return bitmap;
  422. }
  423. }
  424. /// <summary>
  425. /// Starts a task that waits for the next rendering from Chrome.
  426. /// Chrome also renders the page loading, so if you want to see a complete rendering,
  427. /// only start this task once your page is loaded (which you can detect via FrameLoadEnd
  428. /// or your own heuristics based on evaluating JavaScript).
  429. /// It is your responsibility to dispose the returned Bitmap.
  430. /// The bitmap size is determined by the Size property set earlier.
  431. /// </summary>
  432. /// <param name="ignoreExistingScreenshot">Ignore existing bitmap (if any) and return the next available bitmap</param>
  433. /// <param name="blend">Choose which bitmap to retrieve, choose <see cref="PopupBlending.Blend"/> for a merged bitmap.</param>
  434. /// <returns>Task&lt;Bitmap&gt;.</returns>
  435. [Obsolete("Use CaptureScreenshotAsync instead.")]
  436. public Task<Bitmap> ScreenshotAsync(bool ignoreExistingScreenshot = false, PopupBlending blend = PopupBlending.Main)
  437. {
  438. ThrowExceptionIfDisposed();
  439. // Try our luck and see if there is already a screenshot, to save us creating a new thread for nothing.
  440. var screenshot = ScreenshotOrNull(blend);
  441. var completionSource = new TaskCompletionSource<Bitmap>();
  442. if (screenshot == null || ignoreExistingScreenshot)
  443. {
  444. EventHandler<OnPaintEventArgs> afterPaint = null; // otherwise we cannot reference ourselves in the anonymous method below
  445. afterPaint = (sender, e) =>
  446. {
  447. // Chromium has rendered. Tell the task about it.
  448. AfterPaint -= afterPaint;
  449. //If the user handled the Paint event then we'll throw an exception here
  450. //as it's not possible to use ScreenShotAsync as the buffer wasn't updated.
  451. if (e.Handled)
  452. {
  453. completionSource.TrySetException(new InvalidOperationException("OnPaintEventArgs.Handled = true, unable to process request. The buffer has not been updated"));
  454. }
  455. else
  456. {
  457. completionSource.TrySetResultAsync(ScreenshotOrNull(blend));
  458. }
  459. };
  460. AfterPaint += afterPaint;
  461. }
  462. else
  463. {
  464. completionSource.TrySetResultAsync(screenshot);
  465. }
  466. return completionSource.Task;
  467. }
  468. /// <summary>
  469. /// Capture page screenshot.
  470. /// </summary>
  471. /// <param name="format">Image compression format (defaults to png).</param>
  472. /// <param name="quality">Compression quality from range [0..100] (jpeg only).</param>
  473. /// <param name="viewport">view port to capture, if not null the browser will be resized if the requested width/height
  474. /// are larger than the current browser <see cref="Size"/>.</param>
  475. /// <returns>A task that can be awaited to obtain the screenshot as a byte[].</returns>
  476. public async Task<byte[]> CaptureScreenshotAsync(CaptureScreenshotFormat? format = null, int? quality = null, Viewport viewport = null)
  477. {
  478. ThrowExceptionIfDisposed();
  479. ThrowExceptionIfBrowserNotInitialized();
  480. using (var devToolsClient = browser.GetDevToolsClient())
  481. {
  482. if (viewport == null)
  483. {
  484. var screenShot = await devToolsClient.Page.CaptureScreenshotAsync(format, quality, fromSurface: true).ConfigureAwait(continueOnCapturedContext: false);
  485. return screenShot.Data;
  486. }
  487. //https://bitbucket.org/chromiumembedded/cef/issues/3103/offscreen-capture-screenshot-with-devtools
  488. //CEF OSR mode doesn't set the size internally when CaptureScreenShot is called with a clip param specified, so
  489. //we must manually resize our view if size is greater
  490. var newWidth = viewport.Width + viewport.X;
  491. if (newWidth < size.Width)
  492. {
  493. newWidth = size.Width;
  494. }
  495. var newHeight = viewport.Height + viewport.Y;
  496. if (newHeight < size.Height)
  497. {
  498. newHeight = size.Height;
  499. }
  500. var newScale = viewport.Scale;
  501. if (newScale == 0)
  502. {
  503. newScale = deviceScaleFactor;
  504. }
  505. if ((int)newWidth > size.Width || (int)newHeight > size.Height || newScale != deviceScaleFactor)
  506. {
  507. await ResizeAsync((int)newWidth, (int)newHeight, (float)newScale).ConfigureAwait(continueOnCapturedContext:false);
  508. }
  509. //Create a copy instead of modifying users object as we need to set Scale to 1
  510. //as CEF doesn't support passing custom scale.
  511. var clip = new Viewport
  512. {
  513. Height = viewport.Height,
  514. Width = viewport.Width,
  515. X = viewport.X,
  516. Y = viewport.Y,
  517. Scale = 1
  518. };
  519. var response = await devToolsClient.Page.CaptureScreenshotAsync(format, quality, clip: clip, fromSurface: true).ConfigureAwait(continueOnCapturedContext: false);
  520. return response.Data;
  521. }
  522. }
  523. /// <summary>
  524. /// Resize the browser
  525. /// </summary>
  526. /// <param name="width">width</param>
  527. /// <param name="height">height</param>
  528. /// <param name="deviceScaleFactor">device scale factor</param>
  529. /// <returns>A task that can be awaited and will resolve when the desired size is achieved.</returns>
  530. /// <remarks>
  531. /// The current implementation is fairly symplistic, it simply resizes the browser
  532. /// and resolves the task when the browser starts painting at the desired size.
  533. /// </remarks>
  534. public Task ResizeAsync(int width, int height, float? deviceScaleFactor = null)
  535. {
  536. ThrowExceptionIfDisposed();
  537. ThrowExceptionIfBrowserNotInitialized();
  538. if (size.Width == width && size.Height == height && (deviceScaleFactor == null || deviceScaleFactor == DeviceScaleFactor))
  539. {
  540. return Task.FromResult(true);
  541. }
  542. var scaledWidth = (int)(width * DeviceScaleFactor);
  543. var scaledHeight = (int)(height * DeviceScaleFactor);
  544. var tcs = new TaskCompletionSource<bool>();
  545. EventHandler<OnPaintEventArgs> handler = null;
  546. handler = (s, e) =>
  547. {
  548. if (e.Width == scaledWidth && e.Height == scaledHeight)
  549. {
  550. AfterPaint -= handler;
  551. tcs.TrySetResultAsync(true);
  552. }
  553. };
  554. AfterPaint += handler;
  555. //Only set the value if not null otherwise
  556. //a call to NotifyScreenInfoChanged will be made.
  557. if (deviceScaleFactor.HasValue)
  558. {
  559. scaledWidth = (int)(width * deviceScaleFactor.Value);
  560. scaledHeight = (int)(height * deviceScaleFactor.Value);
  561. DeviceScaleFactor = deviceScaleFactor.Value;
  562. }
  563. Size = new Size(width, height);
  564. return tcs.Task;
  565. }
  566. /// <summary>
  567. /// Size of scrollable area in CSS pixels
  568. /// </summary>
  569. /// <returns>A task that can be awaited to get the size of the scrollable area in CSS pixels.</returns>
  570. public async Task<DevTools.DOM.Rect> GetContentSizeAsync()
  571. {
  572. ThrowExceptionIfDisposed();
  573. ThrowExceptionIfBrowserNotInitialized();
  574. using (var devToolsClient = browser.GetDevToolsClient())
  575. {
  576. //Get the content size
  577. var layoutMetricsResponse = await devToolsClient.Page.GetLayoutMetricsAsync().ConfigureAwait(continueOnCapturedContext:false);
  578. return layoutMetricsResponse.CssContentSize;
  579. }
  580. }
  581. /// <inheritdoc/>
  582. public void Load(string url)
  583. {
  584. if (IsDisposed)
  585. {
  586. return;
  587. }
  588. //There's a small window here between CreateBrowser
  589. //and OnAfterBrowserCreated where the Address prop
  590. //will be updated, though LoadUrl won't be called.
  591. if (IsBrowserInitialized)
  592. {
  593. using (var frame = this.GetMainFrame())
  594. {
  595. frame.LoadUrl(url);
  596. }
  597. }
  598. else
  599. {
  600. Address = url;
  601. }
  602. }
  603. /// <summary>
  604. /// The javascript object repository, one repository per ChromiumWebBrowser instance.
  605. /// </summary>
  606. public IJavascriptObjectRepository JavascriptObjectRepository
  607. {
  608. get { return managedCefBrowserAdapter?.JavascriptObjectRepository; }
  609. }
  610. /// <summary>
  611. /// Has Focus - Always False
  612. /// </summary>
  613. /// <returns>returns false</returns>
  614. bool IChromiumWebBrowserBase.Focus()
  615. {
  616. // no control to focus for offscreen browser
  617. return false;
  618. }
  619. /// <summary>
  620. /// Returns the current CEF Browser Instance
  621. /// </summary>
  622. /// <returns>browser instance or null</returns>
  623. public IBrowser GetBrowser()
  624. {
  625. ThrowExceptionIfDisposed();
  626. ThrowExceptionIfBrowserNotInitialized();
  627. return browser;
  628. }
  629. /// <summary>
  630. /// Gets the screen information (scale factor).
  631. /// </summary>
  632. /// <returns>ScreenInfo.</returns>
  633. ScreenInfo? IRenderWebBrowser.GetScreenInfo()
  634. {
  635. return GetScreenInfo();
  636. }
  637. /// <summary>
  638. /// Gets the screen information (scale factor).
  639. /// </summary>
  640. /// <returns>ScreenInfo.</returns>
  641. protected virtual ScreenInfo? GetScreenInfo()
  642. {
  643. return RenderHandler?.GetScreenInfo();
  644. }
  645. /// <summary>
  646. /// Gets the view rect (width, height)
  647. /// </summary>
  648. /// <returns>ViewRect.</returns>
  649. Rect IRenderWebBrowser.GetViewRect()
  650. {
  651. return GetViewRect();
  652. }
  653. /// <summary>
  654. /// Gets the view rect (width, height)
  655. /// </summary>
  656. /// <returns>ViewRect.</returns>
  657. protected virtual Rect GetViewRect()
  658. {
  659. if (RenderHandler == null)
  660. {
  661. return new Rect(0, 0, 640, 480);
  662. }
  663. return RenderHandler.GetViewRect();
  664. }
  665. /// <summary>
  666. /// Called to retrieve the translation from view coordinates to actual screen coordinates.
  667. /// </summary>
  668. /// <param name="viewX">x</param>
  669. /// <param name="viewY">y</param>
  670. /// <param name="screenX">screen x</param>
  671. /// <param name="screenY">screen y</param>
  672. /// <returns>Return true if the screen coordinates were provided.</returns>
  673. bool IRenderWebBrowser.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY)
  674. {
  675. return GetScreenPoint(viewX, viewY, out screenX, out screenY);
  676. }
  677. /// <summary>
  678. /// Called to retrieve the translation from view coordinates to actual screen coordinates.
  679. /// </summary>
  680. /// <param name="viewX">x</param>
  681. /// <param name="viewY">y</param>
  682. /// <param name="screenX">screen x</param>
  683. /// <param name="screenY">screen y</param>
  684. /// <returns>Return true if the screen coordinates were provided.</returns>
  685. protected virtual bool GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY)
  686. {
  687. screenX = 0;
  688. screenY = 0;
  689. return RenderHandler?.GetScreenPoint(viewX, viewY, out screenX, out screenY) ?? false;
  690. }
  691. /// <summary>
  692. /// Called when an element has been rendered to the shared texture handle.
  693. /// This method is only called when <see cref="IWindowInfo.SharedTextureEnabled"/> is set to true
  694. /// </summary>
  695. /// <param name="type">indicates whether the element is the view or the popup widget.</param>
  696. /// <param name="dirtyRect">contains the set of rectangles in pixel coordinates that need to be repainted</param>
  697. /// <param name="sharedHandle">is the handle for a D3D11 Texture2D that can be accessed via ID3D11Device using the OpenSharedResource method.</param>
  698. void IRenderWebBrowser.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, IntPtr sharedHandle)
  699. {
  700. RenderHandler?.OnAcceleratedPaint(type, dirtyRect, sharedHandle);
  701. }
  702. /// <summary>
  703. /// Called when an element should be painted. (Invoked from CefRenderHandler.OnPaint)
  704. /// </summary>
  705. /// <param name="type">indicates whether the element is the view or the popup widget.</param>
  706. /// <param name="dirtyRect">contains the set of rectangles in pixel coordinates that need to be repainted</param>
  707. /// <param name="buffer">The bitmap will be will be width * height *4 bytes in size and represents a BGRA image with an upper-left origin</param>
  708. /// <param name="width">width</param>
  709. /// <param name="height">height</param>
  710. void IRenderWebBrowser.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height)
  711. {
  712. var handled = false;
  713. var args = new OnPaintEventArgs(type == PaintElementType.Popup, dirtyRect, buffer, width, height);
  714. var handler = Paint;
  715. if (handler != null)
  716. {
  717. handler(this, args);
  718. handled = args.Handled;
  719. }
  720. if (!handled)
  721. {
  722. RenderHandler?.OnPaint(type, dirtyRect, buffer, width, height);
  723. }
  724. var afterHandler = AfterPaint;
  725. if (afterHandler != null)
  726. {
  727. afterHandler(this, args);
  728. }
  729. }
  730. /// <summary>
  731. /// Called when the browser's cursor has changed. .
  732. /// </summary>
  733. /// <param name="cursor">If type is Custom then customCursorInfo will be populated with the custom cursor information</param>
  734. /// <param name="type">cursor type</param>
  735. /// <param name="customCursorInfo">custom cursor Information</param>
  736. void IRenderWebBrowser.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
  737. {
  738. RenderHandler?.OnCursorChange(cursor, type, customCursorInfo);
  739. }
  740. /// <summary>
  741. /// Starts dragging.
  742. /// </summary>
  743. /// <param name="dragData">The drag data.</param>
  744. /// <param name="mask">The mask.</param>
  745. /// <param name="x">The x.</param>
  746. /// <param name="y">The y.</param>
  747. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
  748. bool IRenderWebBrowser.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y)
  749. {
  750. return RenderHandler?.StartDragging(dragData, mask, x, y) ?? false;
  751. }
  752. void IRenderWebBrowser.UpdateDragCursor(DragOperationsMask operation)
  753. {
  754. RenderHandler?.UpdateDragCursor(operation);
  755. }
  756. /// <summary>
  757. /// Sets the popup is open.
  758. /// </summary>
  759. /// <param name="show">if set to <c>true</c> [show].</param>
  760. void IRenderWebBrowser.OnPopupShow(bool show)
  761. {
  762. RenderHandler?.OnPopupShow(show);
  763. }
  764. /// <summary>
  765. /// Called when the browser wants to move or resize the popup widget.
  766. /// </summary>
  767. /// <param name="rect">contains the new location and size in view coordinates. </param>
  768. void IRenderWebBrowser.OnPopupSize(Rect rect)
  769. {
  770. RenderHandler?.OnPopupSize(rect);
  771. }
  772. void IRenderWebBrowser.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds)
  773. {
  774. RenderHandler?.OnImeCompositionRangeChanged(selectedRange, characterBounds);
  775. }
  776. void IRenderWebBrowser.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode)
  777. {
  778. RenderHandler?.OnVirtualKeyboardRequested(browser, inputMode);
  779. }
  780. /// <summary>
  781. /// Called when [after browser created].
  782. /// </summary>
  783. /// <param name="browser">The browser.</param>
  784. partial void OnAfterBrowserCreated(IBrowser browser)
  785. {
  786. onAfterBrowserCreatedDelegate?.Invoke(browser);
  787. BrowserInitialized?.Invoke(this, EventArgs.Empty);
  788. }
  789. /// <summary>
  790. /// Sets the address.
  791. /// </summary>
  792. /// <param name="args">The <see cref="AddressChangedEventArgs"/> instance containing the event data.</param>
  793. void IWebBrowserInternal.SetAddress(AddressChangedEventArgs args)
  794. {
  795. Address = args.Address;
  796. AddressChanged?.Invoke(this, args);
  797. }
  798. /// <summary>
  799. /// Sets the loading state change.
  800. /// </summary>
  801. /// <param name="args">The <see cref="LoadingStateChangedEventArgs"/> instance containing the event data.</param>
  802. partial void SetLoadingStateChange(LoadingStateChangedEventArgs args)
  803. {
  804. CanGoBack = args.CanGoBack;
  805. CanGoForward = args.CanGoForward;
  806. IsLoading = args.IsLoading;
  807. }
  808. /// <summary>
  809. /// Sets the title.
  810. /// </summary>
  811. /// <param name="args">The <see cref="TitleChangedEventArgs"/> instance containing the event data.</param>
  812. void IWebBrowserInternal.SetTitle(TitleChangedEventArgs args)
  813. {
  814. TitleChanged?.Invoke(this, args);
  815. }
  816. /// <summary>
  817. /// Sets the tooltip text.
  818. /// </summary>
  819. /// <param name="tooltipText">The tooltip text.</param>
  820. void IWebBrowserInternal.SetTooltipText(string tooltipText)
  821. {
  822. TooltipText = tooltipText;
  823. }
  824. /// <summary>
  825. /// Creates a new bitmap with the dimensions of firstBitmap, then
  826. /// draws the firstBitmap, then overlays the secondBitmap
  827. /// </summary>
  828. /// <param name="firstBitmap">First bitmap, this will be the first image drawn</param>
  829. /// <param name="secondBitmap">Second bitmap, this image will be drawn on the first</param>
  830. /// <param name="secondBitmapPosition">Position of the second bitmap</param>
  831. /// <returns>The merged bitmap, size of firstBitmap</returns>
  832. private Bitmap MergeBitmaps(Bitmap firstBitmap, Bitmap secondBitmap, Point secondBitmapPosition)
  833. {
  834. var mergedBitmap = new Bitmap(firstBitmap.Width, firstBitmap.Height, PixelFormat.Format32bppPArgb);
  835. using (var g = Graphics.FromImage(mergedBitmap))
  836. {
  837. g.DrawImage(firstBitmap, new Rectangle(0, 0, firstBitmap.Width, firstBitmap.Height));
  838. g.DrawImage(secondBitmap, new Rectangle(secondBitmapPosition.X, secondBitmapPosition.Y, secondBitmap.Width, secondBitmap.Height));
  839. }
  840. return mergedBitmap;
  841. }
  842. }
  843. }