"Fossies" - the Fresh Open Source Software Archive

Member "selenium-selenium-4.8.1/dotnet/src/webdriver/DevTools/DevToolsSession.cs" (17 Feb 2023, 28007 Bytes) of package /linux/www/selenium-selenium-4.8.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C# source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "DevToolsSession.cs" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 4.5.0_vs_4.6.0.

    1 // <copyright file="DevToolsSession.cs" company="WebDriver Committers">
    2 // Licensed to the Software Freedom Conservancy (SFC) under one
    3 // or more contributor license agreements. See the NOTICE file
    4 // distributed with this work for additional information
    5 // regarding copyright ownership. The SFC licenses this file
    6 // to you under the Apache License, Version 2.0 (the "License");
    7 // you may not use this file except in compliance with the License.
    8 // You may obtain a copy of the License at
    9 //
   10 //     http://www.apache.org/licenses/LICENSE-2.0
   11 //
   12 // Unless required by applicable law or agreed to in writing, software
   13 // distributed under the License is distributed on an "AS IS" BASIS,
   14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   15 // See the License for the specific language governing permissions and
   16 // limitations under the License.
   17 // </copyright>
   18 
   19 using System;
   20 using System.Collections.Concurrent;
   21 using System.Globalization;
   22 using System.IO;
   23 using System.Net.Http;
   24 using System.Net.WebSockets;
   25 using System.Text;
   26 using System.Threading;
   27 using System.Threading.Tasks;
   28 using Newtonsoft.Json;
   29 using Newtonsoft.Json.Linq;
   30 
   31 namespace OpenQA.Selenium.DevTools
   32 {
   33     /// <summary>
   34     /// Represents a WebSocket connection to a running DevTools instance that can be used to send
   35     /// commands and recieve events.
   36     ///</summary>
   37     public class DevToolsSession : IDevToolsSession
   38     {
   39         public const int AutoDetectDevToolsProtocolVersion = 0;
   40 
   41         private readonly string debuggerEndpoint;
   42         private string websocketAddress;
   43         private readonly TimeSpan openConnectionWaitTimeSpan = TimeSpan.FromSeconds(30);
   44         private readonly TimeSpan closeConnectionWaitTimeSpan = TimeSpan.FromSeconds(2);
   45 
   46         private bool isDisposed = false;
   47         private string attachedTargetId;
   48 
   49         private ClientWebSocket sessionSocket;
   50         private ConcurrentDictionary<long, DevToolsCommandData> pendingCommands = new ConcurrentDictionary<long, DevToolsCommandData>();
   51         private long currentCommandId = 0;
   52 
   53         private DevToolsDomains domains;
   54 
   55         private CancellationTokenSource receiveCancellationToken;
   56         private Task receiveTask;
   57 
   58         /// <summary>
   59         /// Initializes a new instance of the DevToolsSession class, using the specified WebSocket endpoint.
   60         /// </summary>
   61         /// <param name="endpointAddress"></param>
   62         public DevToolsSession(string endpointAddress)
   63         {
   64             if (string.IsNullOrWhiteSpace(endpointAddress))
   65             {
   66                 throw new ArgumentNullException(nameof(endpointAddress));
   67             }
   68 
   69             this.CommandTimeout = TimeSpan.FromSeconds(5);
   70             this.debuggerEndpoint = endpointAddress;
   71             if (endpointAddress.StartsWith("ws:"))
   72             {
   73                 this.websocketAddress = endpointAddress;
   74             }
   75         }
   76 
   77         /// <summary>
   78         /// Event raised when the DevToolsSession logs informational messages.
   79         /// </summary>
   80         public event EventHandler<DevToolsSessionLogMessageEventArgs> LogMessage;
   81 
   82         /// <summary>
   83         /// Event raised an event notification is received from the DevTools session.
   84         /// </summary>
   85         public event EventHandler<DevToolsEventReceivedEventArgs> DevToolsEventReceived;
   86 
   87         /// <summary>
   88         /// Gets or sets the time to wait for a command to complete. Default is 5 seconds.
   89         /// </summary>
   90         public TimeSpan CommandTimeout { get; set; }
   91 
   92         /// <summary>
   93         /// Gets or sets the active session ID of the connection.
   94         /// </summary>
   95         public string ActiveSessionId { get; private set; }
   96 
   97         /// <summary>
   98         /// Gets the endpoint address of the session.
   99         /// </summary>
  100         public string EndpointAddress => this.websocketAddress;
  101 
  102         /// <summary>
  103         /// Gets the version-independent domain implementation for this Developer Tools connection
  104         /// </summary>
  105         public DevToolsDomains Domains => this.domains;
  106 
  107         /// <summary>
  108         /// Gets the version-specific implementation of domains for this DevTools session.
  109         /// </summary>
  110         /// <typeparam name="T">
  111         /// A <see cref="DevToolsSessionDomains"/> object containing the version-specific DevTools Protocol domain implementations.</typeparam>
  112         /// <returns>The version-specific DevTools Protocol domain implementation.</returns>
  113         public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
  114         {
  115             T versionSpecificDomains = this.domains.VersionSpecificDomains as T;
  116             if (versionSpecificDomains == null)
  117             {
  118                 string errorTemplate = "The type is invalid for conversion. You requested domains of type '{0}', but the version-specific domains for this session are '{1}'";
  119                 string exceptionMessage = string.Format(CultureInfo.InvariantCulture, errorTemplate, typeof(T).ToString(), this.domains.GetType().ToString());
  120                 throw new InvalidOperationException(exceptionMessage);
  121             }
  122 
  123             return versionSpecificDomains;
  124         }
  125 
  126         /// <summary>
  127         /// Sends the specified command and returns the associated command response.
  128         /// </summary>
  129         /// <typeparam name="TCommand">A command object implementing the <see cref="ICommand"/> interface.</typeparam>
  130         /// <param name="command">The command to be sent.</param>
  131         /// <param name="cancellationToken">A CancellationToken object to allow for cancellation of the command.</param>
  132         /// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
  133         /// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
  134         /// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
  135         public async Task<ICommandResponse<TCommand>> SendCommand<TCommand>(TCommand command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
  136             where TCommand : ICommand
  137         {
  138             if (command == null)
  139             {
  140                 throw new ArgumentNullException(nameof(command));
  141             }
  142 
  143             var result = await SendCommand(command.CommandName, JToken.FromObject(command), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
  144 
  145             if (result == null)
  146             {
  147                 return null;
  148             }
  149 
  150             if (!this.domains.VersionSpecificDomains.ResponseTypeMap.TryGetCommandResponseType<TCommand>(out Type commandResponseType))
  151             {
  152                 throw new InvalidOperationException($"Type {typeof(TCommand)} does not correspond to a known command response type.");
  153             }
  154 
  155             return result.ToObject(commandResponseType) as ICommandResponse<TCommand>;
  156         }
  157 
  158         /// <summary>
  159         /// Sends the specified command and returns the associated command response.
  160         /// </summary>
  161         /// <typeparam name="TCommand"></typeparam>
  162         /// <typeparam name="TCommandResponse"></typeparam>
  163         /// <typeparam name="TCommand">A command object implementing the <see cref="ICommand"/> interface.</typeparam>
  164         /// <param name="cancellationToken">A CancellationToken object to allow for cancellation of the command.</param>
  165         /// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
  166         /// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
  167         /// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
  168         public async Task<TCommandResponse> SendCommand<TCommand, TCommandResponse>(TCommand command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
  169             where TCommand : ICommand
  170             where TCommandResponse : ICommandResponse<TCommand>
  171         {
  172             if (command == null)
  173             {
  174                 throw new ArgumentNullException(nameof(command));
  175             }
  176 
  177             var result = await SendCommand(command.CommandName, JToken.FromObject(command), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
  178 
  179             if (result == null)
  180             {
  181                 return default(TCommandResponse);
  182             }
  183 
  184             return result.ToObject<TCommandResponse>();
  185         }
  186 
  187         /// <summary>
  188         /// Returns a JToken based on a command created with the specified command name and params.
  189         /// </summary>
  190         /// <param name="commandName">The name of the command to send.</param>
  191         /// <param name="commandParameters">The parameters of the command as a JToken object</param>
  192         /// <param name="cancellationToken">A CancellationToken object to allow for cancellation of the command.</param>
  193         /// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
  194         /// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
  195         /// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
  196         //[DebuggerStepThrough]
  197         public async Task<JToken> SendCommand(string commandName, JToken commandParameters, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
  198         {
  199             if (millisecondsTimeout.HasValue == false)
  200             {
  201                 millisecondsTimeout = Convert.ToInt32(CommandTimeout.TotalMilliseconds);
  202             }
  203 
  204             if (this.attachedTargetId == null)
  205             {
  206                 LogTrace("Session not currently attached to a target; reattaching");
  207                 await this.InitializeSession();
  208             }
  209 
  210             var message = new DevToolsCommandData(Interlocked.Increment(ref this.currentCommandId), this.ActiveSessionId, commandName, commandParameters);
  211 
  212             if (this.sessionSocket != null && this.sessionSocket.State == WebSocketState.Open)
  213             {
  214                 LogTrace("Sending {0} {1}: {2}", message.CommandId, message.CommandName, commandParameters.ToString());
  215 
  216                 var contents = JsonConvert.SerializeObject(message);
  217                 var contentBuffer = Encoding.UTF8.GetBytes(contents);
  218 
  219                 this.pendingCommands.TryAdd(message.CommandId, message);
  220                 await this.sessionSocket.SendAsync(new ArraySegment<byte>(contentBuffer), WebSocketMessageType.Text, true, cancellationToken);
  221 
  222                 var responseWasReceived = await Task.Run(() => message.SyncEvent.Wait(millisecondsTimeout.Value, cancellationToken));
  223 
  224                 if (!responseWasReceived && throwExceptionIfResponseNotReceived)
  225                 {
  226                     throw new InvalidOperationException($"A command response was not received: {commandName}");
  227                 }
  228 
  229                 DevToolsCommandData modified;
  230                 if (this.pendingCommands.TryRemove(message.CommandId, out modified))
  231                 {
  232                     if (modified.IsError)
  233                     {
  234                         var errorMessage = modified.Result.Value<string>("message");
  235                         var errorData = modified.Result.Value<string>("data");
  236 
  237                         var exceptionMessage = $"{commandName}: {errorMessage}";
  238                         if (!string.IsNullOrWhiteSpace(errorData))
  239                         {
  240                             exceptionMessage = $"{exceptionMessage} - {errorData}";
  241                         }
  242 
  243                         LogTrace("Recieved Error Response {0}: {1} {2}", modified.CommandId, message, errorData);
  244                         throw new CommandResponseException(exceptionMessage)
  245                         {
  246                             Code = modified.Result.Value<long>("code")
  247                         };
  248                     }
  249 
  250                     return modified.Result;
  251                 }
  252             }
  253             else
  254             {
  255                 if (this.sessionSocket != null)
  256                 {
  257                     LogTrace("WebSocket is not connected (current state is {0}); not sending {1}", this.sessionSocket.State, message.CommandName);
  258                 }
  259             }
  260 
  261             return null;
  262         }
  263 
  264         /// <summary>
  265         /// Disposes of the DevToolsSession and frees all resources.
  266         ///</summary>
  267         public void Dispose()
  268         {
  269             this.Dispose(true);
  270         }
  271 
  272         /// <summary>
  273         /// Asynchronously starts the session.
  274         /// </summary>
  275         /// <param name="requestedProtocolVersion">The requested version of the protocol to use in communicating with the browswer.</param>
  276         /// <returns>A task that represents the asynchronous operation.</returns>
  277         internal async Task StartSession(int requestedProtocolVersion)
  278         {
  279             int protocolVersion = await InitializeProtocol(requestedProtocolVersion);
  280             this.domains = DevToolsDomains.InitializeDomains(protocolVersion, this);
  281             await this.InitializeSocketConnection();
  282             await this.InitializeSession();
  283             try
  284             {
  285                 // Wrap this in a try-catch, because it's not the end of the
  286                 // world if clearing the log doesn't work.
  287                 await this.domains.Log.Clear();
  288                 LogTrace("Log cleared.", this.attachedTargetId);
  289             }
  290             catch (WebDriverException)
  291             {
  292             }
  293         }
  294 
  295         /// <summary>
  296         /// Asynchronously stops the session.
  297         /// </summary>
  298         /// <param name="manualDetach"><see langword="true"/> to manually detach the session
  299         /// from its attached target; otherswise <see langword="false""/>.</param>
  300         /// <returns>A task that represents the asynchronous operation.</returns>
  301         internal async Task StopSession(bool manualDetach)
  302         {
  303             if (this.attachedTargetId != null)
  304             {
  305                 this.Domains.Target.TargetDetached -= this.OnTargetDetached;
  306                 string sessionId = this.ActiveSessionId;
  307                 this.ActiveSessionId = null;
  308                 if (manualDetach)
  309                 {
  310                     await this.Domains.Target.DetachFromTarget(sessionId, this.attachedTargetId);
  311                 }
  312 
  313                 this.attachedTargetId = null;
  314             }
  315         }
  316 
  317         protected void Dispose(bool disposing)
  318         {
  319             if (!this.isDisposed)
  320             {
  321                 if (disposing)
  322                 {
  323                     this.Domains.Target.TargetDetached -= this.OnTargetDetached;
  324                     this.pendingCommands.Clear();
  325                     this.TerminateSocketConnection();
  326 
  327                     // Note: Canceling the receive task will dispose of
  328                     // the underlying ClientWebSocket instance.
  329                     this.CancelReceiveTask();
  330                 }
  331 
  332                 this.isDisposed = true;
  333             }
  334         }
  335 
  336         private async Task<int> InitializeProtocol(int requestedProtocolVersion)
  337         {
  338             int protocolVersion = requestedProtocolVersion;
  339             if (this.websocketAddress == null)
  340             {
  341                 string debuggerUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}", this.debuggerEndpoint);
  342                 string rawVersionInfo = string.Empty;
  343                 using (HttpClient client = new HttpClient())
  344                 {
  345                     client.BaseAddress = new Uri(debuggerUrl);
  346                     rawVersionInfo = await client.GetStringAsync("/json/version");
  347                 }
  348 
  349                 var versionInfo = JsonConvert.DeserializeObject<DevToolsVersionInfo>(rawVersionInfo);
  350                 this.websocketAddress = versionInfo.WebSocketDebuggerUrl;
  351 
  352                 if (requestedProtocolVersion == AutoDetectDevToolsProtocolVersion)
  353                 {
  354                     bool versionParsed = int.TryParse(versionInfo.BrowserMajorVersion, out protocolVersion);
  355                     if (!versionParsed)
  356                     {
  357                         throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, "Unable to parse version number received from browser. Reported browser version string is '{0}'", versionInfo.Browser));
  358                     }
  359                 }
  360             }
  361             else
  362             {
  363                 if (protocolVersion == AutoDetectDevToolsProtocolVersion)
  364                 {
  365                     throw new WebDriverException("A WebSocket address for DevTools protocol has been detected, but the protocol version cannot be automatically detected. You must specify a protocol version.");
  366                 }
  367             }
  368 
  369             return protocolVersion;
  370         }
  371 
  372         private async Task InitializeSocketConnection()
  373         {
  374             LogTrace("Creating WebSocket");
  375             this.sessionSocket = new ClientWebSocket();
  376             this.sessionSocket.Options.KeepAliveInterval = TimeSpan.Zero;
  377 
  378             try
  379             {
  380                 var timeoutTokenSource = new CancellationTokenSource(this.openConnectionWaitTimeSpan);
  381                 await this.sessionSocket.ConnectAsync(new Uri(this.websocketAddress), timeoutTokenSource.Token);
  382                 while (this.sessionSocket.State != WebSocketState.Open && !timeoutTokenSource.Token.IsCancellationRequested) ;
  383             }
  384             catch (OperationCanceledException e)
  385             {
  386                 throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, "Could not establish WebSocket connection within {0} seconds.", this.openConnectionWaitTimeSpan.TotalSeconds), e);
  387             }
  388 
  389             LogTrace("WebSocket created; starting message listener");
  390             this.receiveCancellationToken = new CancellationTokenSource();
  391             this.receiveTask = Task.Run(() => ReceiveMessage().ConfigureAwait(false));
  392         }
  393 
  394         private async Task InitializeSession()
  395         {
  396             LogTrace("Creating session");
  397             if (this.attachedTargetId == null)
  398             {
  399                 // Set the attached target ID to a "pending connection" value
  400                 // (any non-null will do, so we choose the empty string), so
  401                 // that when getting the available targets, we won't
  402                 // recursively try to call InitializeSession.
  403                 this.attachedTargetId = "";
  404                 var targets = await this.domains.Target.GetTargets();
  405                 foreach (var target in targets)
  406                 {
  407                     if (target.Type == "page")
  408                     {
  409                         this.attachedTargetId = target.TargetId;
  410                         LogTrace("Found Target ID {0}.", this.attachedTargetId);
  411                         break;
  412                     }
  413                 }
  414             }
  415 
  416             if (this.attachedTargetId == "")
  417             {
  418                 this.attachedTargetId = null;
  419                 throw new WebDriverException("Unable to find target to attach to, no taargets of type 'page' available");
  420             }
  421 
  422             string sessionId = await this.domains.Target.AttachToTarget(this.attachedTargetId);
  423             LogTrace("Target ID {0} attached. Active session ID: {1}", this.attachedTargetId, sessionId);
  424             this.ActiveSessionId = sessionId;
  425 
  426             await this.domains.Target.SetAutoAttach();
  427             LogTrace("AutoAttach is set.", this.attachedTargetId);
  428 
  429             this.domains.Target.TargetDetached += this.OnTargetDetached;
  430         }
  431 
  432         private async void OnTargetDetached(object sender, TargetDetachedEventArgs e)
  433         {
  434             if (e.SessionId == this.ActiveSessionId && e.TargetId == this.attachedTargetId)
  435             {
  436                 await this.StopSession(false);
  437             }
  438         }
  439 
  440         private void TerminateSocketConnection()
  441         {
  442             if (this.sessionSocket != null && this.sessionSocket.State == WebSocketState.Open)
  443             {
  444                 var closeConnectionTokenSource = new CancellationTokenSource(this.closeConnectionWaitTimeSpan);
  445                 try
  446                 {
  447                     // Since Chromium-based DevTools does not respond to the close
  448                     // request with a correctly echoed WebSocket close packet, but
  449                     // rather just terminates the socket connection, so we have to
  450                     // catch the exception thrown when the socket is terminated
  451                     // unexpectedly. Also, because we are using async, waiting for
  452                     // the task to complete might throw a TaskCanceledException,
  453                     // which we should also catch. Additiionally, there are times
  454                     // when mulitple failure modes can be seen, which will throw an
  455                     // AggregateException, consolidating several exceptions into one,
  456                     // and this too must be caught. Finally, the call to CloseAsync
  457                     // will hang even though the connection is already severed.
  458                     // Wait for the task to complete for a short time (since we're
  459                     // restricted to localhost, the default of 2 seconds should be
  460                     // plenty; if not, change the initialization of the timout),
  461                     // and if the task is still running, then we assume the connection
  462                     // is properly closed.
  463                     LogTrace("Sending socket close request");
  464                     Task closeTask = Task.Run(async () => await this.sessionSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, closeConnectionTokenSource.Token));
  465                     closeTask.Wait();
  466                 }
  467                 catch (WebSocketException)
  468                 {
  469                 }
  470                 catch (TaskCanceledException)
  471                 {
  472                 }
  473                 catch (AggregateException)
  474                 {
  475                 }
  476             }
  477         }
  478 
  479         private void CancelReceiveTask()
  480         {
  481             if (this.receiveTask != null)
  482             {
  483                 // Wait for the recieve task to be completely exited (for
  484                 // whatever reason) before attempting to dispose it. Also
  485                 // note that canceling the receive task will dispose of the
  486                 // underlying WebSocket.
  487                 this.receiveCancellationToken.Cancel();
  488                 this.receiveTask.Wait();
  489                 this.receiveTask.Dispose();
  490                 this.receiveTask = null;
  491             }
  492         }
  493 
  494         private async Task ReceiveMessage()
  495         {
  496             var cancellationToken = this.receiveCancellationToken.Token;
  497             try
  498             {
  499                 var buffer = WebSocket.CreateClientBuffer(1024, 1024);
  500                 while (this.sessionSocket.State != WebSocketState.Closed && !cancellationToken.IsCancellationRequested)
  501                 {
  502                     WebSocketReceiveResult result = await this.sessionSocket.ReceiveAsync(buffer, cancellationToken);
  503                     if (!cancellationToken.IsCancellationRequested)
  504                     {
  505                         if (result.MessageType == WebSocketMessageType.Close && this.sessionSocket.State == WebSocketState.CloseReceived)
  506                         {
  507                             LogTrace("Got WebSocket close message from browser");
  508                             await this.sessionSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken);
  509                         }
  510                     }
  511 
  512                     if (this.sessionSocket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close)
  513                     {
  514                         using (var stream = new MemoryStream())
  515                         {
  516                             stream.Write(buffer.Array, 0, result.Count);
  517                             while (!result.EndOfMessage)
  518                             {
  519                                 result = await this.sessionSocket.ReceiveAsync(buffer, cancellationToken);
  520                                 stream.Write(buffer.Array, 0, result.Count);
  521                             }
  522 
  523                             stream.Seek(0, SeekOrigin.Begin);
  524                             using (var reader = new StreamReader(stream, Encoding.UTF8))
  525                             {
  526                                 string message = reader.ReadToEnd();
  527                                 ProcessIncomingMessage(message);
  528                             }
  529                         }
  530                     }
  531                 }
  532             }
  533             catch (OperationCanceledException)
  534             {
  535             }
  536             catch (WebSocketException)
  537             {
  538             }
  539             finally
  540             {
  541                 this.sessionSocket.Dispose();
  542                 this.sessionSocket = null;
  543             }
  544         }
  545 
  546         private void ProcessIncomingMessage(string message)
  547         {
  548             var messageObject = JObject.Parse(message);
  549 
  550             if (messageObject.TryGetValue("id", out var idProperty))
  551             {
  552                 var commandId = idProperty.Value<long>();
  553 
  554                 DevToolsCommandData commandInfo;
  555                 if (this.pendingCommands.TryGetValue(commandId, out commandInfo))
  556                 {
  557                     if (messageObject.TryGetValue("error", out var errorProperty))
  558                     {
  559                         commandInfo.IsError = true;
  560                         commandInfo.Result = errorProperty;
  561                     }
  562                     else
  563                     {
  564                         commandInfo.Result = messageObject["result"];
  565                         LogTrace("Recieved Response {0}: {1}", commandId, commandInfo.Result.ToString());
  566                     }
  567 
  568                     commandInfo.SyncEvent.Set();
  569                 }
  570                 else
  571                 {
  572                     LogError("Recieved Unknown Response {0}: {1}", commandId, message);
  573                 }
  574 
  575                 return;
  576             }
  577 
  578             if (messageObject.TryGetValue("method", out var methodProperty))
  579             {
  580                 var method = methodProperty.Value<string>();
  581                 var methodParts = method.Split(new char[] { '.' }, 2);
  582                 var eventData = messageObject["params"];
  583 
  584                 LogTrace("Recieved Event {0}: {1}", method, eventData.ToString());
  585                 OnDevToolsEventReceived(new DevToolsEventReceivedEventArgs(methodParts[0], methodParts[1], eventData));
  586                 return;
  587             }
  588 
  589             LogTrace("Recieved Other: {0}", message);
  590         }
  591 
  592         private void OnDevToolsEventReceived(DevToolsEventReceivedEventArgs e)
  593         {
  594             if (DevToolsEventReceived != null)
  595             {
  596                 DevToolsEventReceived(this, e);
  597             }
  598         }
  599 
  600         private void LogTrace(string message, params object[] args)
  601         {
  602             if (LogMessage != null)
  603             {
  604                 LogMessage(this, new DevToolsSessionLogMessageEventArgs(DevToolsSessionLogLevel.Trace, message, args));
  605             }
  606         }
  607 
  608         private void LogError(string message, params object[] args)
  609         {
  610             if (LogMessage != null)
  611             {
  612                 LogMessage(this, new DevToolsSessionLogMessageEventArgs(DevToolsSessionLogLevel.Error, message, args));
  613             }
  614         }
  615     }
  616 }