Obteniendo la URL de la pestaña actual de Google Chrome usando C #

Solía ​​haber una manera de obtener la URL de la pestaña activa de Google Chrome utilizando FindWindowEx en combinación con una llamada a SendMessage para obtener el texto actualmente en el cuadro multifunción. Parece que una actualización reciente (?) Ha roto este método, ya que Chrome parece estar renderizando todo ahora. (Puede consultar con Spy ++, AHK Window Spy o Window Detective)

Para obtener la URL actual en Firefox y Opera, puede usar DDE y WWW_GetWindowInfo . Esto no parece ser posible en Chrome (¿ya?).

Esta pregunta tiene una respuesta con más información sobre cómo solía funcionar, que es esta pieza de código (que, como he explicado, ya no funciona – hAddressBox es 0 ):

 var hAddressBox = FindWindowEx( intPtr, IntPtr.Zero, "Chrome_OmniboxView", IntPtr.Zero); var sb = new StringBuilder(256); SendMessage(hAddressBox, 0x000D, (IntPtr)256, sb); temp = sb.ToString(); 

Entonces mi pregunta es: ¿hay una nueva forma de obtener la URL de la pestaña actualmente enfocada? (Solo el titulo no es suficiente)

Editar: Parece que el código en mi respuesta aquí ya no funciona (aunque la idea de usar AutomationElement todavía funciona) para las versiones posteriores de Chrome, así que busque en las otras respuestas las diferentes versiones. Por ejemplo, aquí hay uno para Chrome 54: https://stackoverflow.com/a/40638519/377618

El siguiente código parece funcionar, (gracias al comentario de icemanind) pero sin embargo requiere muchos recursos. Se necesitan unos 350 ms para encontrar elmUrlBar … un poco lento.

Sin mencionar que tenemos el problema de trabajar con múltiples procesos de chrome que se ejecutan al mismo tiempo.

 // there are always multiple chrome processes, so we have to loop through all of them to find the // process with a Window Handle and an automation element of name "Address and search bar" Process[] procsChrome = Process.GetProcessesByName("chrome"); foreach (Process chrome in procsChrome) { // the chrome process must have a window if (chrome.MainWindowHandle == IntPtr.Zero) { continue; } // find the automation element AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle); AutomationElement elmUrlBar = elm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar")); // if it can be found, get the value from the URL bar if (elmUrlBar != null) { AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns(); if (patterns.Length > 0) { ValuePattern val = (ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0]); Console.WriteLine("Chrome URL found: " + val.Current.Value); } } } 

Editar: no estaba contento con el método lento anterior, así que lo hice más rápido (ahora 50 ms) y agregué un poco de validación de URL para asegurarme de que obtuviéramos la URL correcta en lugar de algo que el usuario podría estar buscando en la web, o aún estar ocupado escribiendo la URL. Aquí está el código:

 // there are always multiple chrome processes, so we have to loop through all of them to find the // process with a Window Handle and an automation element of name "Address and search bar" Process[] procsChrome = Process.GetProcessesByName("chrome"); foreach (Process chrome in procsChrome) { // the chrome process must have a window if (chrome.MainWindowHandle == IntPtr.Zero) { continue; } // find the automation element AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle); // manually walk through the tree, searching using TreeScope.Descendants is too slow (even if it's more reliable) AutomationElement elmUrlBar = null; try { // walking path found using inspect.exe (Windows SDK) for Chrome 31.0.1650.63 m (currently the latest stable) var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome")); if (elm1 == null) { continue; } // not the right chrome.exe // here, you can optionally check if Incognito is enabled: //bool bIncognito = TreeWalker.RawViewWalker.GetFirstChild(TreeWalker.RawViewWalker.GetFirstChild(elm1)) != null; var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :( var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "")); var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar)); elmUrlBar = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom)); } catch { // Chrome has probably changed something, and above walking needs to be modified. :( // put an assertion here or something to make sure you don't miss it continue; } // make sure it's valid if (elmUrlBar == null) { // it's not.. continue; } // elmUrlBar is now the URL bar element. we have to make sure that it's out of keyboard focus if we want to get a valid URL if ((bool)elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty)) { continue; } // there might not be a valid pattern to use, so we have to make sure we have one AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns(); if (patterns.Length == 1) { string ret = ""; try { ret = ((ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0])).Current.Value; } catch { } if (ret != "") { // must match a domain name (and possibly "https://" in front) if (Regex.IsMatch(ret, @"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$")) { // prepend http:// to the url, because Chrome hides it if it's not SSL if (!ret.StartsWith("http")) { ret = "http://" + ret; } Console.WriteLine("Open Chrome URL found: '" + ret + "'"); } } continue; } } 

A partir de Chrome 54, el siguiente código me funciona:

 public static string GetActiveTabUrl() { Process[] procsChrome = Process.GetProcessesByName("chrome"); if (procsChrome.Length < = 0) return null; foreach (Process proc in procsChrome) { // the chrome process must have a window if (proc.MainWindowHandle == IntPtr.Zero) continue; // to find the tabs we first need to locate something reliable - the 'New Tab' button AutomationElement root = AutomationElement.FromHandle(proc.MainWindowHandle); var SearchBar = root.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar")); if (SearchBar != null) return (string)SearchBar.GetCurrentPropertyValue(ValuePatternIdentifiers.ValueProperty); } return null; } 

Obtuve los resultados de Chrome 38.0.2125.10 con el siguiente código (el código dentro del bloque ‘try’ debe ser reemplazado por este)

 var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome")); if (elm1 == null) { continue; } // not the right chrome.exe var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "TopContainerView")); var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar)); var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "LocationBarView")); elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit)); 

Tomé la solución de Angelo y la limpie un poco … Tengo una fijación con LINQ 🙂

Este es el método principal por así decirlo; usa un par de métodos de extensión:

 public IEnumerable GetTabs() { // there are always multiple chrome processes, so we have to loop through all of them to find the // process with a Window Handle and an automation element of name "Address and search bar" var processes = Process.GetProcessesByName("chrome"); var automationElements = from chrome in processes where chrome.MainWindowHandle != IntPtr.Zero select AutomationElement.FromHandle(chrome.MainWindowHandle); return from element in automationElements select element.GetUrlBar() into elmUrlBar where elmUrlBar != null where !((bool) elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty)) let patterns = elmUrlBar.GetSupportedPatterns() where patterns.Length == 1 select elmUrlBar.TryGetValue(patterns) into ret where ret != "" where Regex.IsMatch(ret, @"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$") select ret.StartsWith("http") ? ret : "http://" + ret; } 

Tenga en cuenta que el comentario es engañoso, ya que los comentarios tienden a ser, en realidad no se ve en un solo AutomationElement. Lo dejé allí porque el código de Angelo lo tenía.

Aquí está la clase de extensión:

 public static class AutomationElementExtensions { public static AutomationElement GetUrlBar(this AutomationElement element) { try { return InternalGetUrlBar(element); } catch { // Chrome has probably changed something, and above walking needs to be modified. :( // put an assertion here or something to make sure you don't miss it return null; } } public static string TryGetValue(this AutomationElement urlBar, AutomationPattern[] patterns) { try { return ((ValuePattern) urlBar.GetCurrentPattern(patterns[0])).Current.Value; } catch { return ""; } } // private static AutomationElement InternalGetUrlBar(AutomationElement element) { // walking path found using inspect.exe (Windows SDK) for Chrome 29.0.1547.76 m (currently the latest stable) var elm1 = element.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome")); var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :( var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "")); var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar)); var result = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom)); return result; } } 

Refiriéndose a la solución de Angelo Geels, aquí hay un parche para la versión 35: el código dentro del bloque “try” debe ser reemplazado por este:

 var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome")); if (elm1 == null) { continue; } // not the right chrome.exe var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "")); var elm4 = TreeWalker.RawViewWalker.GetNextSibling(elm3); // I don't know a Condition for this for finding var elm7 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar)); elmUrlBar = elm7.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom)); 

Lo tomé desde aquí: http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~Chrome.txt

Todos los métodos anteriores están fallando para mí con Chrome V53 y superior.

Esto es lo que funciona:

 Process[] procsChrome = Process.GetProcessesByName("chrome"); foreach (Process chrome in procsChrome) { if (chrome.MainWindowHandle == IntPtr.Zero) continue; AutomationElement element = AutomationElement.FromHandle(chrome.MainWindowHandle); if (element == null) return null; Condition conditions = new AndCondition( new PropertyCondition(AutomationElement.ProcessIdProperty, chrome.Id), new PropertyCondition(AutomationElement.IsControlElementProperty, true), new PropertyCondition(AutomationElement.IsContentElementProperty, true), new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit)); AutomationElement elementx = element.FindFirst(TreeScope.Descendants, conditions); return ((ValuePattern)elementx.GetCurrentPattern(ValuePattern.Pattern)).Current.Value as string; } 

Lo encontré aquí:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/93001bf5-440b-4a3a-ad6c-478a4f618e32/how-can-i-get-urls-of-open-pages-from-chrome- y-firefox? forum = csharpgeneral

Para mí, solo la ventana activa de Chrome tiene MainWindowHandle. Lo solucioné mirando a través de todas las ventanas en busca de ventanas de cromo y luego usando esas manijas. Por ejemplo:

  public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam); [DllImport("user32.dll")] protected static extern bool EnumWindows(Win32Callback enumProc, IntPtr lParam); private static bool EnumWindow(IntPtr handle, IntPtr pointer) { List pointers = GCHandle.FromIntPtr(pointer).Target as List; pointers.Add(handle); return true; } private static List GetAllWindows() { Win32Callback enumCallback = new Win32Callback(EnumWindow); List pointers = new List(); GCHandle listHandle = GCHandle.Alloc(pointers); try { EnumWindows(enumCallback, GCHandle.ToIntPtr(listHandle)); } finally { if (listHandle.IsAllocated) listHandle.Free(); } return pointers; } 

Y luego para obtener todas las ventanas de Chrome:

  [DllImport("User32", CharSet = CharSet.Auto, SetLastError = true)] public static extern int GetWindowText(IntPtr windowHandle, StringBuilder stringBuilder, int nMaxCount); [DllImport("user32.dll", EntryPoint = "GetWindowTextLength", SetLastError = true)] internal static extern int GetWindowTextLength(IntPtr hwnd); private static string GetTitle(IntPtr handle) { int length = GetWindowTextLength(handle); StringBuilder sb = new StringBuilder(length + 1); GetWindowText(handle, sb, sb.Capacity); return sb.ToString(); } 

y finalmente:

 GetAllWindows() .Select(GetTitle) .Where(x => x.Contains("Google Chrome")) .ToList() .ForEach(Console.WriteLine); 

Con suerte, esto le ahorra a alguien más algo de tiempo para descubrir cómo obtener los identificadores de todas las ventanas de Chrome.

Para la versión 53.0.2785 lo tengo trabajando con esto:

 var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome")); if (elm1 == null) { continue; } // not the right chrome.exe var elm2 = elm1.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1]; var elm3 = elm2.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1]; var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "principal")); var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "")); elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));