From ed530a89fda8c1212e85046460127f8f0ae493fd Mon Sep 17 00:00:00 2001 From: Bjoern Esswein <bjoern.esswein@gmail.com> Date: Fri, 6 Oct 2023 22:13:47 +0200 Subject: [PATCH] WIP: added devtools screencast and devtools events TODO: implement receiving events --- BrowserView.prefab | 12 +- Runtime/BrowserView.cs | 15 +- Runtime/ChromeDevtools/Browser.cs | 8 +- Runtime/ChromeDevtools/Protocol/Cast.meta | 8 + Runtime/ChromeDevtools/Protocol/Cast/Cast.cs | 124 ++++++++++++++++ .../ChromeDevtools/Protocol/Cast/Cast.cs.meta | 11 ++ .../Protocol/DevtoolsCommand.cs | 15 -- .../ChromeDevtools/Protocol/DevtoolsEvent.cs | 23 +++ .../Protocol/DevtoolsEvent.cs.meta | 11 ++ .../Protocol/DevtoolsResponse.cs | 23 +++ .../Protocol/DevtoolsResponse.cs.meta | 11 ++ .../Protocol/Page/Screencast.cs | 137 ++++++++++++++++++ .../Protocol/Page/Screencast.cs.meta | 11 ++ 13 files changed, 377 insertions(+), 32 deletions(-) create mode 100644 Runtime/ChromeDevtools/Protocol/Cast.meta create mode 100644 Runtime/ChromeDevtools/Protocol/Cast/Cast.cs create mode 100644 Runtime/ChromeDevtools/Protocol/Cast/Cast.cs.meta create mode 100644 Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs create mode 100644 Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs.meta create mode 100644 Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs create mode 100644 Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs.meta create mode 100644 Runtime/ChromeDevtools/Protocol/Page/Screencast.cs create mode 100644 Runtime/ChromeDevtools/Protocol/Page/Screencast.cs.meta diff --git a/BrowserView.prefab b/BrowserView.prefab index dbdb259..732b376 100644 --- a/BrowserView.prefab +++ b/BrowserView.prefab @@ -10,8 +10,8 @@ GameObject: m_Component: - component: {fileID: 2974656142881083530} - component: {fileID: 5559415116192402672} - - component: {fileID: 4531839590213585742} - - component: {fileID: 5511584970398147175} + - component: {fileID: 3767983593885104582} + - component: {fileID: 2459857651630437765} m_Layer: 5 m_Name: BrowserView m_TagString: Untagged @@ -47,7 +47,7 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8825460134736404412} m_CullTransparentMesh: 1 ---- !u!114 &4531839590213585742 +--- !u!114 &3767983593885104582 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -67,14 +67,14 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Texture: {fileID: 0} + m_Texture: {fileID: 2800000, guid: 7ee48c5fba4919649a8a6094cbead669, type: 3} m_UVRect: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 ---- !u!114 &5511584970398147175 +--- !u!114 &2459857651630437765 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -86,4 +86,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6893fa42f929f894286c6f00c40bcd06, type: 3} m_Name: m_EditorClassIdentifier: - targetUrl: https://google.de + headlessBrowser: 1 diff --git a/Runtime/BrowserView.cs b/Runtime/BrowserView.cs index 86ad1fb..32bc7be 100644 --- a/Runtime/BrowserView.cs +++ b/Runtime/BrowserView.cs @@ -1,28 +1,29 @@ using ChromeDevTools; using System.Collections; -using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; -//using Unity.Networking.Transport; [RequireComponent(typeof(RawImage))] -public class BrowserView : MonoBehaviour //TODO: Extends RawImage instead? +public class BrowserView : MonoBehaviour { private Browser browser; private BrowserTab tab; private RawImage rawImage; + public bool headlessBrowser = true; + //TODO: handle changed targetUrl public string targetUrl = "https://google.de"; // Start is called before the first frame update - void Start() + private void Start() { rawImage = this.gameObject.GetComponent<RawImage>(); } - void OnEnable() + private void OnEnable() { + Browser.headless = headlessBrowser; browser = Browser.getInstance(); //StartCoroutine(GetOpenTabs()); @@ -45,7 +46,7 @@ public class BrowserView : MonoBehaviour //TODO: Extends RawImage instead? } // Update is called once per frame - void Update() + private void Update() { } @@ -65,7 +66,7 @@ public class BrowserView : MonoBehaviour //TODO: Extends RawImage instead? /** * Close all browser windows. */ - void OnApplicationQuit() + private void OnApplicationQuit() { tab.Close(); browser.Close(); diff --git a/Runtime/ChromeDevtools/Browser.cs b/Runtime/ChromeDevtools/Browser.cs index 49fad21..b57c646 100644 --- a/Runtime/ChromeDevtools/Browser.cs +++ b/Runtime/ChromeDevtools/Browser.cs @@ -17,8 +17,8 @@ namespace ChromeDevTools private static Process browserProcess; /* browser settings */ - private const string browserExecutablePath = "chrome"; - private const bool headlessBrowser = false; + public static string browserExecutablePath = "chrome"; + public static bool headless = true; private const int debugPort = 9222; /* JsonSerializer */ @@ -63,10 +63,10 @@ namespace ChromeDevTools { Browser.browserProcess = new Process(); Browser.browserProcess.StartInfo.FileName = browserExecutablePath; - Browser.browserProcess.StartInfo.Arguments = $"--user-data-dir={Path.Join(Application.temporaryCachePath, "BrowserView")} --remote-debugging-port={debugPort} --remote-allow-origins=http://localhost:{debugPort}"; + Browser.browserProcess.StartInfo.Arguments = $"--user-data-dir={Path.Join(Application.temporaryCachePath, "BrowserView")} --remote-debugging-port={debugPort} --remote-allow-origins=http://localhost:{debugPort} --hide-crash-restore-bubble"; // set headlessBrowser to false to see the browser window - if (headlessBrowser) + if (headless) { Browser.browserProcess.StartInfo.Arguments = string.Concat(Browser.browserProcess.StartInfo.Arguments, " --headless=new"); } diff --git a/Runtime/ChromeDevtools/Protocol/Cast.meta b/Runtime/ChromeDevtools/Protocol/Cast.meta new file mode 100644 index 0000000..f9da9a2 --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/Cast.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1f4e0afa13be5aa4da5b06d9d861002a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ChromeDevtools/Protocol/Cast/Cast.cs b/Runtime/ChromeDevtools/Protocol/Cast/Cast.cs new file mode 100644 index 0000000..4a67cb3 --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/Cast/Cast.cs @@ -0,0 +1,124 @@ +using Newtonsoft.Json; + +namespace ChromeDevTools +{ + namespace Protocol + { + namespace Cast + { + } + } +} + + +/* +Devtools protocol.json: +{ + "domain": "Cast", + "description": "A domain for interacting with Cast, Presentation API, and Remote Playback API\nfunctionalities.", + "experimental": true, + "types": [ + { + "id": "Sink", + "type": "object", + "properties": [ + { + "name": "name", + "type": "string" + }, + { + "name": "id", + "type": "string" + }, + { + "name": "session", + "description": "Text describing the current session. Present only if there is an active\nsession on the sink.", + "optional": true, + "type": "string" + } + ] + } + ], + "commands": [ + { + "name": "enable", + "description": "Starts observing for sinks that can be used for tab mirroring, and if set,\nsinks compatible with |presentationUrl| as well. When sinks are found, a\n|sinksUpdated| event is fired.\nAlso starts observing for issue messages. When an issue is added or removed,\nan |issueUpdated| event is fired.", + "parameters": [ + { + "name": "presentationUrl", + "optional": true, + "type": "string" + } + ] + }, + { + "name": "disable", + "description": "Stops observing for sinks and issues." + }, + { + "name": "setSinkToUse", + "description": "Sets a sink to be used when the web page requests the browser to choose a\nsink via Presentation API, Remote Playback API, or Cast SDK.", + "parameters": [ + { + "name": "sinkName", + "type": "string" + } + ] + }, + { + "name": "startDesktopMirroring", + "description": "Starts mirroring the desktop to the sink.", + "parameters": [ + { + "name": "sinkName", + "type": "string" + } + ] + }, + { + "name": "startTabMirroring", + "description": "Starts mirroring the tab to the sink.", + "parameters": [ + { + "name": "sinkName", + "type": "string" + } + ] + }, + { + "name": "stopCasting", + "description": "Stops the active Cast session on the sink.", + "parameters": [ + { + "name": "sinkName", + "type": "string" + } + ] + } + ], + "events": [ + { + "name": "sinksUpdated", + "description": "This is fired whenever the list of available sinks changes. A sink is a\ndevice or a software surface that you can cast to.", + "parameters": [ + { + "name": "sinks", + "type": "array", + "items": { + "$ref": "Sink" + } + } + ] + }, + { + "name": "issueUpdated", + "description": "This is fired whenever the outstanding issue/error message changes.\n|issueMessage| is empty if there is no issue.", + "parameters": [ + { + "name": "issueMessage", + "type": "string" + } + ] + } + ] +}*/ \ No newline at end of file diff --git a/Runtime/ChromeDevtools/Protocol/Cast/Cast.cs.meta b/Runtime/ChromeDevtools/Protocol/Cast/Cast.cs.meta new file mode 100644 index 0000000..d2b9e03 --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/Cast/Cast.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b4f546ce82c7cb84b8003bc93d5cf73f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs index 0fd27d7..1601a85 100644 --- a/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs +++ b/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs @@ -25,20 +25,5 @@ namespace ChromeDevTools public DevtoolsCommandWrapper(T command) => Params = command; } public interface IDevtoolsCommand {} - - public class DevtoolsResponseWrapper - { - public long Id { get; set; } - public JObject Result { get; set; } - } - /// - /// Every devtools command response has the same id and a method as the corresponding command - /// - public class DevtoolsResponseWrapper<T>: DevtoolsResponseWrapper where T: IDevtoolsResponse - { - public new T Result { get; set; } - } - - public interface IDevtoolsResponse {} } } \ No newline at end of file diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs new file mode 100644 index 0000000..a4c124e --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json.Linq; +using System; + +namespace ChromeDevTools +{ + namespace Protocol + { + public class DevtoolsEventWrapper + { + public string Method { get; set; } + public JObject Params { get; set; } + } + /// + /// Every devtools command response has the same id and a method as the corresponding command + /// + public class DevtoolsEventWrapper<T> : DevtoolsEventWrapper where T : IDevtoolsEvent + { + public new T Params { get; set; } + } + + public interface IDevtoolsEvent { } + } +} \ No newline at end of file diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs.meta b/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs.meta new file mode 100644 index 0000000..e4ed691 --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/DevtoolsEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 399f2cceede40aa4ebe787a7f276de78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs new file mode 100644 index 0000000..8d4f55c --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json.Linq; +using System; + +namespace ChromeDevTools +{ + namespace Protocol + { + public class DevtoolsResponseWrapper + { + public long Id { get; set; } + public JObject Result { get; set; } + } + /// + /// Every devtools command response has the same id and a method as the corresponding command + /// + public class DevtoolsResponseWrapper<T> : DevtoolsResponseWrapper where T : IDevtoolsResponse + { + public new T Result { get; set; } + } + + public interface IDevtoolsResponse { } + } +} \ No newline at end of file diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs.meta b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs.meta new file mode 100644 index 0000000..ea0de6b --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 81e639e1e8ae9084ab868731d8c5742f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ChromeDevtools/Protocol/Page/Screencast.cs b/Runtime/ChromeDevtools/Protocol/Page/Screencast.cs new file mode 100644 index 0000000..d3dac2e --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/Page/Screencast.cs @@ -0,0 +1,137 @@ +using Newtonsoft.Json; + +namespace ChromeDevTools +{ + namespace Protocol + { + namespace Page + { + /// <summary> + /// Starts sending each frame using the `screencastFrame` event. + /// "experimental": true + /// + /// No Response + /// </summary> + //[CommandResponseAttribute(typeof(StartScreencastCommandResponse))] + public class startScreencast : IDevtoolsCommand + { + /// <summary> + /// Gets or sets Image compression format (defaults to png). + /// Can be "jpeg" or "png" + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Format { get; set; } + /// <summary> + /// Gets or sets Compression quality from range [0..100]. + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public int? Quality { get; set; } + /// <summary> + /// Maximum screenshot width. + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public int MaxWidth { get; set; } + /// <summary> + /// Maximum screenshot height. + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public int maxHeight { get; set; } + /// <summary> + /// Send every n-th frame. + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public int everyNthFrame { get; set; } + } + + + /// <summary> + /// Acknowledges that a screencast frame has been received by the frontend. + /// "experimental": true + /// </summary> + public class screencastFrameAck : IDevtoolsCommand + { + /// <summary> + /// Frame number. + /// </summary> + public int sessionId { get; set; } + } + + /// <summary> + /// Stops sending each frame in the `screencastFrame`. + /// "experimental": true + /// </summary> + public class stopScreencast : IDevtoolsCommand + { } + + /// <summary> + /// Compressed image data requested by the `startScreencast`. + /// </summary> + public class screencastFrameEvent : IDevtoolsEvent + { + /// <summary> + /// Gets or sets Base64-encoded image data. + /// </summary> + public string Data { get; set; } + /// <summary> + /// Screencast frame metadata. + /// </summary> + public Types.ScreencastFrameMetadata metadata { get; set; } + /// <summary> + /// Frame number. + /// </summary> + public int sessionId { get; set; } + } + + /// <summary> + /// Fired when the page with currently enabled screencast was shown or hidden `. + /// </summary> + public class screencastVisibilityChanged : IDevtoolsEvent + { + /// <summary> + /// True if the page is visible. + /// </summary> + public bool visible { get; set; } + } + + namespace Types + { + /// <summary> + /// Screencast frame metadata. + /// </summary> + public class ScreencastFrameMetadata + { + /// <summary> + /// Top offset in DIP. + /// </summary> + public int offsetTop { get; set; } + /// <summary> + /// Page scale factor. + /// </summary> + public int pageScaleFactor { get; set; } + /// <summary> + /// Device screen width in DIP. + /// </summary> + public int deviceWidth { get; set; } + /// <summary> + /// Device screen height in DIP. + /// </summary> + public int deviceHeight { get; set; } + /// <summary> + /// Position of horizontal scroll in CSS pixels. + /// </summary> + public int scrollOffsetX { get; set; } + /// <summary> + /// Position of vertical scroll in CSS pixels. + /// </summary> + public int scrollOffsetY { get; set; } + /// <summary> + /// Frame swap timestamp. + /// UTC time in seconds, counted from January 1, 1970. + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public long? timestamp { get; set; } + } + } + } + } +} \ No newline at end of file diff --git a/Runtime/ChromeDevtools/Protocol/Page/Screencast.cs.meta b/Runtime/ChromeDevtools/Protocol/Page/Screencast.cs.meta new file mode 100644 index 0000000..157140b --- /dev/null +++ b/Runtime/ChromeDevtools/Protocol/Page/Screencast.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f7a2406fccd8c74d89d419e99a8bf29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: -- GitLab