diff --git a/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs b/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs
index fe7e7104ce703a04b3e9acf125e77d553c875b6b..737ffe341b6bcb820180248fe7d930099315dead 100644
--- a/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs
+++ b/Runtime/ChromeDevtools/DevtoolsProtocolHandler.cs
@@ -196,6 +196,36 @@ namespace bessw.Unity.WebView.ChromeDevTools
             yield return new WaitUntil(() => sendTask.IsCompleted);
         }
 
+        /// <summary>
+        /// json serializes and sends a command over the devtools websocket
+        /// </summary>
+        /// <typeparam name="C">IDevtoolsCommandWithResponse</typeparam>
+        /// <typeparam name="R">IDevtoolsResponse</typeparam>
+        /// <param name="command"></param>
+        /// <returns>Task that resoves with the response</returns>
+        public Task<R> SendCommandAsync<C, R>(C command) where C : IDevtoolsCommandWithResponse where R : class, IDevtoolsResponse
+        {
+            // apply the message wrapper
+            var wrappedCommand = new DevtoolsCommandWrapper<C>(command)
+            {
+                Id = ++LAST_COMMAND_ID
+            };
+
+            TaskCompletionSource<R> tcs = new TaskCompletionSource<R>();
+            
+            // register the response callback for this command id
+            if (!commandResponseDict.TryAdd(wrappedCommand.Id, new ResponseTypeAndCallback(typeof(R), (res) => tcs.SetResult(res as R))))
+            {
+                throw new InvalidOperationException($"could not add response callback for command '{wrappedCommand.Id}' to commandResponseDict");
+            }
+
+            // json serialize the command and send it
+            var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings);
+            Debug.Log($"ws send: '{json}'");
+            devtools.SendCommandAsync(json);
+            return tcs.Task;
+        }
+
         /// <summary>
         /// json serializes and sends a command over the devtools websocket
         /// </summary>
diff --git a/Runtime/ChromeDevtools/DevtoolsWebsocket.cs b/Runtime/ChromeDevtools/DevtoolsWebsocket.cs
index c4e05260e2963b8caac3a1cdd91937503ed2ee7d..b6565d6db24a1a39001269e78c3873f18e2067b7 100644
--- a/Runtime/ChromeDevtools/DevtoolsWebsocket.cs
+++ b/Runtime/ChromeDevtools/DevtoolsWebsocket.cs
@@ -1,4 +1,3 @@
-using bessw.Unity.WebView.ChromeDevTools;
 using System;
 using System.IO;
 using System.Net.WebSockets;
diff --git a/Runtime/ChromeDevtools/DomNodeWrapper.cs b/Runtime/ChromeDevtools/DomNodeWrapper.cs
index 5f97af3b7ffa972b7fe4cac3751b94d2f37c7cf0..c59666ea08cb811f13befbc3de192eabed42b2ba 100644
--- a/Runtime/ChromeDevtools/DomNodeWrapper.cs
+++ b/Runtime/ChromeDevtools/DomNodeWrapper.cs
@@ -2,7 +2,6 @@ using bessw.Unity.WebView.ChromeDevTools.Protocol.DOM;
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Linq;
 using System.Threading.Tasks;
 
 #nullable enable
@@ -169,6 +168,86 @@ namespace bessw.Unity.WebView.ChromeDevTools
 
         #endregion Event Handlers
 
+        #region Async Commands
+
+        /// <summary>
+        /// get the root node of the document
+        /// </summary>
+        /// <param name="tab"></param>
+        public static async Task<DomNodeWrapper> getDocumentAsync(BrowserTab tab)
+        {
+            var commandResponse = await tab.devtools.SendCommandAsync<getDocument, getDocumentCommandResponse>(new getDocument());
+            var documentRoot = commandResponse.root;
+
+            var domNode = createOrUpdateNode(tab,
+                documentRoot.nodeId,
+                documentRoot.parentId,
+                documentRoot.backendNodeId,
+                documentRoot
+            );
+            return domNode;
+        }
+
+        /// <summary>
+        /// get a node by its js object id
+        /// </summary>
+        /// <param name="tab"></param>
+        /// <param name="objectId">Javascript html object id</param>
+        public static async Task<DomNodeWrapper> describeNodeAsync(BrowserTab tab, string objectId, int? depth = null, bool? pierce = null)
+        {
+            var commandResponse = await tab.devtools.SendCommandAsync<describeNode, describeNodeCommandResponse>(new describeNode
+            {
+                objectId = objectId,
+                depth = depth,
+                pierce = pierce
+            });
+            return createOrUpdateNode(tab, commandResponse.node.nodeId, null, null, commandResponse.node);
+        }
+
+        /// <summary>
+        /// get the nodeId by its js object id
+        /// </summary>
+        /// <param name="tab"></param>
+        /// <param name="objectId">Javascript html object id</param>
+        public static async Task<DomNodeWrapper> requestNodeAsync(BrowserTab tab, string objectId)
+        {
+            var commandResponse = await tab.devtools.SendCommandAsync<requestNode, requestNodeCommandResponse>(new requestNode
+            {
+                objectId = objectId
+            });
+            return createOrUpdateNode(tab, commandResponse.nodeId);
+        }
+
+
+        public async Task<DomNodeWrapper[]> querySelectorAllAsync(string selector)
+        {
+            var commandResponse = await tab.devtools.SendCommandAsync<querySelectorAll, querySelectorAllCommandResponse>(new querySelectorAll
+            {
+                nodeId = NodeId,
+                selector = selector
+            });
+            var nodeIds = commandResponse.nodeIds;
+            var domNodes = new DomNodeWrapper[nodeIds.Length];
+            for (int i = 0; i < nodeIds.Length; i++)
+            {
+                domNodes[i] = createOrUpdateNode(tab, nodeIds[i]);
+            }
+            return domNodes;
+        }
+
+
+        public async Task<Dictionary<string, string>> getAttributesAsync()
+        {
+            var commandResponse = await tab.devtools.SendCommandAsync<getAttributes, getAttributesCommandResponse>(new getAttributes
+            {
+                nodeId = NodeId
+            });
+            Node.attributes = commandResponse.attributes;
+            return commandResponse.attributes;
+        }
+
+        #endregion Async Commands
+
         #region Commands
 
         /// <summary>
diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs
index ac790ad3a74926196da35ae926d11960b914d595..def8bdbba961ec86d3e70b2c05e31347f4755622 100644
--- a/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs
+++ b/Runtime/ChromeDevtools/Protocol/DevtoolsResponse.cs
@@ -1,5 +1,4 @@
 using Newtonsoft.Json.Linq;
-using System;
 
 namespace bessw.Unity.WebView.ChromeDevTools.Protocol
 {
diff --git a/Runtime/WebViewComponent.cs b/Runtime/WebViewComponent.cs
index 76dad434bc110f261ea9eb7b7e536010ac9184dd..7865670bf11799fdb46fbb44b9d4edcf026350d3 100644
--- a/Runtime/WebViewComponent.cs
+++ b/Runtime/WebViewComponent.cs
@@ -1,9 +1,11 @@
 using bessw.Unity.WebView.ChromeDevTools;
 using bessw.Unity.WebView.ChromeDevTools.Protocol.Input;
+using MoreLinq;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Serialization;
 using System.Collections;
 using System.Collections.Generic;
+using System.Linq;
 using UnityEngine;
 using UnityEngine.EventSystems;
 using UnityEngine.UI;
@@ -39,7 +41,7 @@ namespace bessw.Unity.WebView
         #endregion json serializer
 
         private Browser browser;
-        protected BrowserTab tab;
+        public BrowserTab tab;
 
         private RawImage rawImage;
         private RectTransform rectTransform;
@@ -87,32 +89,6 @@ namespace bessw.Unity.WebView
             });
         }
 
-        private IEnumerator getDropzoneState ()
-        {
-            Debug.LogWarning($"dropzone pre");
-
-            DomNodeWrapper doc = null;
-            yield return DomNodeWrapper.getDocument(tab, (document) => {
-                Debug.LogWarning($"dropzone 1: '{document}'");
-                doc = document;
-                
-                StartCoroutine(document.querySelectorAll("[dropzone='copy']", (dropzones) => {
-                    foreach (var dropzone in dropzones)
-                    {
-                        Debug.LogWarning($"dropzone 2: Node is Null?: '{dropzone.Node == null}'");
-                        StartCoroutine(dropzone.getAttributes((attributes) =>
-                        {
-                            Debug.LogWarning($"dropzone 3 getAttributes: '{string.Join(", ", attributes.Values)}'");
-                        }));
-                    }
-                }));
-            });
-            Debug.LogWarning($"dropzone post: '{doc}'");
-
-            // alternative way to get the dropzone state
-
-        }
-
         // Update is called once per frame
         private void Update()
         {
@@ -162,9 +138,6 @@ namespace bessw.Unity.WebView
         {
             Debug.LogWarning($"OnDrop: {eventData.position}");
             createDragEvent(DragEventType.drop, eventData);
-
-            // TODO: remove debug code
-            StartCoroutine(getDropzoneState());
         }
 
         public void OnPointerExit(PointerEventData eventData)