context: #region WatiN Copyright (C) 2006-2011 Jeroen van Menen
  
  //Copyright 2006-2011 Jeroen van Menen
  //
  //   Licensed under the Apache License, Version 2.0 (the "License");
  //   you may not use this file except in compliance with the License.
  //   You may obtain a copy of the License at
  //
  //       http://www.apache.org/licenses/LICENSE-2.0
  //
  //   Unless required by applicable law or agreed to in writing, software
  //   distributed under the License is distributed on an "AS IS" BASIS,
  //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  //   See the License for the specific language governing permissions and
  //   limitations under the License.
  
  #endregion Copyright
  
  # if INCLUDE_CHROME
  
  namespace WatiN.Core.Native.Chrome
  {
      using System;
      using System.Collections;
      using System.Collections.Generic;
      using System.Diagnostics;
      using System.IO;
      using System.Net;
      using System.Net.Sockets;
      using System.Text;
      
      using Logging;
  
      using UtilityClasses;
      
      /// <summary>
      /// Handles telnet communication to the Chrome browser shell.
      /// </summary>
      public class ChromeClientPort : ClientPortBase
      {
          /// <summary>
          /// List of known errors  that the Google remote port might return.
          /// </summary>
          private static readonly List<string> knownResponseErrors = new List<string> { "unknown command", "TypeError", "SyntaxError", "\"Uncaught SyntaxError", "Uncaught ReferenceError", "true\r\r\n\"Not allowed to load local resource: file:///" };
  
          /// <summary>
          /// The port used to connect to chrome.
          /// </summary>
          private const int ChromePort = 9999;
  
          /// <summary>
          /// <c>true</c> if the <see cref="Dispose()"/> method has been called to release resources.
          /// </summary>
          private bool disposed;
  
          /// <summary>
          /// Underlying socket used to create a <see cref="NetworkStream"/>.
          /// </summary>
          private Socket telnetSocket;
  
          /// <summary>
          /// Finalizes an instance of the <see cref="ChromeClientPort"/> class. 
          /// Releases unmanaged resources and performs other cleanup operations before the
          /// <see cref="FireFox"/> is reclaimed by garbage collection.
          /// </summary>
          ~ChromeClientPort()
          {
              this.Dispose(false);
          }
  
          /// <summary>
          /// Gets a value indicating whether this <see cref="ChromeClientPort"/> is connected.
          /// </summary>
          /// <value><c>true</c> if connected; otherwise, <c>false</c>.</value>
          public bool Connected { get; private set; }
  
          /// <summary>
          /// Gets the name of the javascript variable that references the DOM:document object.
          /// </summary>
          public override string DocumentVariableName
          {
              get
              {
                  return "document";
              }
          }
  
          /// <summary>
          /// Gets the type of java script engine.
          /// </summary>
          /// <value>The type of java script engine.</value>
          public override JavaScriptEngineType JavaScriptEngine
          {
              get
              {
                  return JavaScriptEngineType.WebKit;
              }
          }
  
          /// <summary>
          /// Gets the name of the browser variable.
          /// </summary>
          /// <value>The name of the browser variable.</value>
          public override string BrowserVariableName
          {
              get
              {
                  throw new System.NotImplementedException();
              }
          }
  
          /// <summary>
          /// Gets or sets the last command send to the chrome client.
          /// </summary>
          /// <value>The last command send.</value>
          private string LastCommandSend
          {
              get;
              set;
          }
  
          /// <summary>
          /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
          /// </summary>
          /// <filterpriority>2</filterpriority>
          public override void Dispose()
          {
              this.Dispose(true);
  
              // This object will be cleaned up by the Dispose method.
              // Therefore, you should call GC.SupressFinalize to
              // take this object off the finalization queue 
              // and prevent finalization code for this object
              // from executing a second time.
              GC.SuppressFinalize(this);
          }
  
          /// <summary>
          /// Connects to the Chrome browser and navigates to the specified URL.
          /// </summary>
          /// <param name="url">The URL to connect to.</param>
          public override void Connect(string url)
          {
              this.ValidateCanConnect();
              this.disposed = false;
  
              Logger.LogDebug(string.Format("Attempting to connect to chrome on localhost port {0}.", ChromePort));
  
              this.LastResponse = string.Empty;
              this.Response = new StringBuilder();
  
              this.Process = Core.Chrome.CreateProcess(string.Format("--disable-hang-monitor --remote-shell-port={0} \"{1}\"", ChromePort, url), true);            
  
              this.telnetSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { Blocking = true };
  
              try
              {
                  this.telnetSocket.Connect(IPAddress.Parse("127.0.0.1"), ChromePort);
              }
              catch (SocketException sockException)
              {
                  Logger.LogDebug(string.Format("Failed connecting to jssh server.\nError code:{0}\nError message:{1}", sockException.ErrorCode, sockException.Message));
                  throw new ChromeException("Unable to connect to jssh server, please make sure you have correctly installed the jssh.xpi plugin", sockException);
              }
  
              this.Connected = true;
              this.WaitForConnectionEstablished();
              Logger.LogDebug(string.Format("Successfully connected to Chrome on port.{0}", ChromePort));
              
              //DefineDefaultJSVariables();
          }
  
          /// <summary>
          /// Initializes the document.
          /// </summary>
          public override void InitializeDocument()
          {            
          }
  
          /// <summary>
          /// Writes the specified data to the remote server.
          /// </summary>
          /// <param name="data">The data to write.</param>
          /// <param name="resultExpected"><c>true</c> if a result is expected.</param>
          /// <param name="checkForErrors"><c>true</c> if error checking should be applied.</param>
          /// <param name="args">Arguments to format with the data.</param>
          protected override void SendAndRead(string data, bool resultExpected, bool checkForErrors, params object[] args)
          {
              if (string.IsNullOrEmpty(data))
              {
                  return;
              }
  
              var command = UtilityClass.StringFormat(data, args);
  
              if (!command.StartsWith("debug()") && !command.StartsWith("exit"))
              {
                  command = "print " + command;
              }            
  
              this.SendCommand(command);
              this.ReadResponse(resultExpected, checkForErrors);
          }
  
          /// <summary>
          /// Releases unmanaged and - optionally - managed resources
          /// </summary>
          /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
          protected void Dispose(bool disposing)
          {
              // Check to see if Dispose has already been called.
              if (!this.disposed)
              {
                  // If disposing equals true, dispose all managed 
                  // and unmanaged resources.
                  if (disposing)
                  {
                      // Dispose managed resources.
                      if (this.telnetSocket != null && this.telnetSocket.Connected && !this.Process.HasExited)
                      {
                          try
                          {
  
gt:                             Logger.LogDebug("Closing connection to chrome");