web-dev-qa-db-fra.com

Obtenir l'URL de l'onglet en cours à partir de Google Chrome en utilisant C #

Auparavant, il était possible d'obtenir l'URL de l'onglet actif à partir de Google Chrome en utilisant FindWindowEx en combinaison avec un appel SendMessage pour obtenir le texte actuellement dans la boîte polyvalente. Une récente mise à jour (?) Semble avoir rompu cette méthode, car Chrome semble tout rendre lui-même maintenant. (Vous pouvez vérifier avec Spy ++, AHK Window Spy ou Window Detective)

Pour obtenir l'URL actuelle sur Firefox et Opera, vous pouvez utiliser DDE et WWW_GetWindowInfo. Cela ne semble pas être possible sur Chrome (plus?).

Cette question a une réponse avec plus d’informations sur la façon dont cela fonctionnait, qui est ce morceau de code (qui, comme je l’ai expliqué, ne fonctionne plus - hAddressBox est 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();

Ma question est donc la suivante: existe-t-il un new moyen d'obtenir l'URL de l'onglet actuellement ciblé? (Juste le titre ne suffit pas)

40
Codecat

Edit: On dirait que le code dans ma réponse ici ne fonctionne plus (bien que l'idée d'utiliser AutomationElement fonctionne toujours) pour les dernières versions de Chrome. Recherchez donc les différentes réponses dans les autres versions. Par exemple, en voici un pour Chrome 54: https://stackoverflow.com/a/40638519/377618

Le code suivant semble fonctionner (grâce au commentaire de icemanind) mais nécessite toutefois beaucoup de ressources. Il faut environ 350 ms pour trouver elmUrlBar ... un peu lent.

Sans oublier que nous avons le problème de travailler avec plusieurs processus chrome exécutés en même temps.

// 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);
    }
  }
}

Edit: Je n'étais pas satisfait de la méthode lente ci-dessus, je l'ai donc accélérée (maintenant 50 ms) et ajouté une validation d'URL pour nous assurer que nous obtenions l'URL correcte au lieu de quelque chose que l'utilisateur pourrait rechercher Web, ou encore occupé à taper l'URL. Voici le code:

// 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;
  }
}
34
Codecat

À partir de Chrome 54, le code suivant fonctionne pour moi:

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;
}
8
dotNET

J'ai obtenu des résultats pour Chrome 38.0.2125.10 avec le code suivant (le code À l'intérieur du bloc 'try' doit être remplacé par celui-ci)

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));
6
Paul3k

Toutes les méthodes ci-dessus échouent pour moi avec Chrome V53 et versions ultérieures.

Voici ce qui fonctionne:

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;
}

Je l'ai trouvé ici:

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

5
Randall Deetz

J'ai pris la solution d'Angelo et l'ai nettoyée un peu ... J'ai une fixation avec LINQ :)

C'est la méthode principale pour ainsi dire; il utilise quelques méthodes d'extension:

public IEnumerable<string> 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;
}

Notez que le commentaire est trompeur, car les commentaires ont tendance à l'être: il ne prend pas en compte un seul AutomationElement. Je l'ai laissé là parce que le code d'Angelo l'avait.

Voici la classe d'extension:

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;
  }
}
4
Marcel Popescu

En référence à la solution de Angelo Geels, voici un correctif pour la version 35 - le code à l'intérieur du bloc "try" doit être remplacé par ceci:

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));  

Je l’ai tirée d’ici: http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~Chrome.Chapitre

3
Michal Czardybon

Pour moi, seule la fenêtre chromée active a un MainWindowHandle. J'ai contourné ce problème en cherchant des fenêtres chromées dans toutes les fenêtres, puis en utilisant ces poignées. Par exemple:

    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<IntPtr> pointers = GCHandle.FromIntPtr(pointer).Target as List<IntPtr>;
        pointers.Add(handle);
        return true;
    }

    private static List<IntPtr> GetAllWindows()
    {
        Win32Callback enumCallback = new Win32Callback(EnumWindow);
        List<IntPtr> pointers = new List<IntPtr>();
        GCHandle listHandle = GCHandle.Alloc(pointers);
        try
        {
            EnumWindows(enumCallback, GCHandle.ToIntPtr(listHandle));
        }
        finally
        {
            if (listHandle.IsAllocated) listHandle.Free();
        }
        return pointers;
    }

Et puis pour obtenir toutes les fenêtres chromées:

    [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();
    }

et enfin:

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

Espérons que cela épargnera à quelqu'un d'autre un peu de temps pour déterminer comment obtenir les poignées de toutes les fenêtres chromées.

2
yeerk

J'ai découvert ce post et j'ai réussi à extraire l'URL de chrome en C # en utilisant ces méthodes, merci à tous! 

_ {Malheureusement avec la récente mise à jour Chrome 69, la traversée de l'arborescence AutomationElement a été interrompue à nouveau.

J'ai trouvé cet article de Microsoft: Naviguer parmi les éléments UI Automation avec TreeWalker

Et l’a utilisé pour créer une fonction simple qui recherche la variable AutomationElement avec le type de contrôle "edit" recherché, au lieu de parcourir une hiérarchie d’arbres en constante évolution}, puis d’extraire la valeur URL cette AutomationElement

J'ai écrit un cours simple qui résume tout cela: Google-Chrome-URL-Check-C-Sharp .

Le readme explique un peu comment l'utiliser. 

Dans l’ensemble, il pourrait être un peu plus fiable et espère que certains d’entre vous le trouveront utile. 

1
progietheus

Pour la version 53.0.2785, cela fonctionne:

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));
0
Yind