23

I'm writing a system tray app that needs to check if an internal web based app is open.

I can check IE using the following:

SHDocVw.ShellWindows shellWindows = new SHDocVw.ShellWindows();
        string filename;
        bool sdOpen = false;
        foreach (SHDocVw.InternetExplorer ie in shellWindows)
        {
            filename = Path.GetFileNameWithoutExtension(ie.FullName).ToLower();
            if (filename.Equals("iexplore"))
            {
                string[] urlParts = (ie.LocationURL.ToString()).Split('/');
                string website = urlParts[2];
                if (website == "myApp:8080") { sdOpen = true; };
            }
        }

        if (sdOpen) { Console.WriteLine("App is open"); } else { Console.WriteLine("App is not open"); };

        Console.ReadKey(true);

However, some of the users using the system prefer Chrome or Firefox.

How can I do the same as above (i.e. get the urls of any open tabs in the browser) for Chrome and Firefox? (I'm not going to bother with other browsers as these are the only ones in use in our organisation.)

Ben
  • 4,281
  • 8
  • 62
  • 103
  • Interesting question. I've found a thread about [doing it with AutoHotkey](http://www.autohotkey.com/forum/topic51981.html) with Firefox (current tab), the discussion there may help you. You may create Firefox addon that will iterate over all the tabs, and similarly with Chrome. [This](http://www.autohotkey.com/forum/topic46974.html) might be useful as well. – jakub.g Oct 18 '11 at 23:06
  • More links: [MozRepl @ AMO](https://addons.mozilla.org/pl/firefox/addon/mozrepl/), [YouTube video](http://www.youtube.com/watch?v=5RSnHN6S52c). To list all the URLs of opened tabs in Firefox, see [this extension](https://addons.mozilla.org/en-US/firefox/addon/list-open-urls/). Firefox extensions are basically ZIP files containing JavaScript and XUL code, so you can easily customize it. – jakub.g Oct 18 '11 at 23:13
  • 1
    How about inspecting users' internet traffic instead? You may setup a [proxy](http://stackoverflow.com/questions/226784/how-to-create-a-simple-proxy-in-c), for example. – Dmitriy Oct 24 '11 at 11:18
  • You can refer [Guideline](http://www.codeproject.com/Articles/35859/Detect-and-prevent-multiple-windows-or-tab-usage-i) – Chintan Jan 23 '12 at 16:51

4 Answers4

23

It's specific for every browser. That's for the major ones:

  • Internet Explorer - You can use SHDocVw (like you did)
  • Firefox - You can get the URL using DDE (source below)
  • Chrome - You can get the URL while enumerating all the child windows untill you get to the control with class "Chrome_OmniboxView" and then get the text using GetWindowText
  • Opera - You can use the same thing as Firefox, but with "opera"
  • Safari - There is no known method since it uses custom drawn controls

EDIT: Since 2014, Chrome has changed and you need to get the URL with Acessibility.

Code to get the URL from Firefox/Opera using DDE (which used NDDE - the only good DDE wrapper for .NET):

//
// usage: GetBrowserURL("opera") or GetBrowserURL("firefox")
//

private string GetBrowserURL(string browser) {
    try {
        DdeClient dde = new DdeClient(browser, "WWW_GetWindowInfo");
        dde.Connect();
        string url = dde.Request("URL", int.MaxValue);
        string[] text = url.Split(new string[] { "\",\"" }, StringSplitOptions.RemoveEmptyEntries);
        dde.Disconnect();
        return text[0].Substring(1);
    } catch {
        return null;
    }
}
blez
  • 4,939
  • 5
  • 50
  • 82
  • 1
    For Chrome this possibly changed in a later version? All the child windows of `Chrome_WidgitWin_1` are of type `Static` and don't have a title set. – Steven Jeuris Mar 28 '14 at 16:46
  • Even with FireFox, even without the string.Split, the provided DDE code only returns the currently active tab. It does not enumerate all tabs. Not to mention the string.split is buggy (it will not return the correct URL if your URL has a double quote in it for example, since it becomes escaped for DDE transport)... – BrainSlugs83 Aug 04 '14 at 21:48
  • For Opera it does not work as for Firefox, I added here how to do it: http://stackoverflow.com/questions/21968177/get-url-from-opera-browser – Lucian Oct 07 '14 at 11:44
  • 2
    The DDE method for Firefox has stopped working since version 49 :( – Jon Limjap Jan 03 '17 at 10:27
  • Hello @JonLimjap, Did you find another way to get the URL info? – Ayorus Jan 15 '18 at 23:14
  • @Ayorus, You can use Accessibility to get the URL in Firefox. For example in Firefox Quantum the name of the Address bar is "Search with {Search Engine} or enter address" where {Search Engine} might be Google, Bing or any other search engine. You can check if the name of the field you are searching for starts with Search with and ends with or enter address. Notice that in this method it will only work for english versions. In other languages the name of this field might be different. – SyndicatorBBB May 22 '18 at 10:43
  • Thanks for your answer @JonLimjap. After a while I did manage to solve that problem using UIAutomation – Ayorus May 22 '18 at 14:35
6

Using UIAutomation - get urls for FireFox and Chrome:

 else if (browser == BrowserType.Chrome)
        {
            //"Chrome_WidgetWin_1"

            Process[] procsChrome = Process.GetProcessesByName("chrome");
            foreach (Process chrome in procsChrome)
            {
                // the chrome process must have a window
                if (chrome.MainWindowHandle == IntPtr.Zero)
                {
                    continue;
                }
                //AutomationElement elm = AutomationElement.RootElement.FindFirst(TreeScope.Children,
                //         new PropertyCondition(AutomationElement.ClassNameProperty, "Chrome_WidgetWin_1"));
                // 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 29.0.1547.76 m (currently the latest stable)
                    var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
                    var elm2 = TreeWalker.ControlViewWalker.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.NameProperty, "Address and search bar"));
                }
                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;
                            }
                            return ret;
                        }
                    }
                    continue;
                }
            }

        }
        else if (browser == BrowserType.Firefox)
        {
            AutomationElement root = AutomationElement.RootElement.FindFirst(TreeScope.Children,
                new PropertyCondition(AutomationElement.ClassNameProperty, "MozillaWindowClass"));

                Condition toolBar = new AndCondition(
                new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar),
                new PropertyCondition(AutomationElement.NameProperty, "Browser tabs"));
                var tool = root.FindFirst(TreeScope.Children, toolBar);

                var tool2 = TreeWalker.ControlViewWalker.GetNextSibling(tool);

                var children = tool2.FindAll(TreeScope.Children, Condition.TrueCondition);

                foreach (AutomationElement item in children)
                {
                    foreach (AutomationElement i in item.FindAll(TreeScope.Children, Condition.TrueCondition))
                    {
                        foreach (AutomationElement ii in i.FindAll(TreeScope.Element, Condition.TrueCondition))
                        {
                            if (ii.Current.LocalizedControlType == "edit")
                            {
                                if (!ii.Current.BoundingRectangle.X.ToString().Contains("empty"))
                                {
                                    ValuePattern activeTab = ii.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
                                    var activeUrl = activeTab.Current.Value;
                                    return activeUrl;
                                }
                            }
                        }
                    }
                }
            }
Ibizanhound
  • 91
  • 2
  • 12
cablehead
  • 151
  • 2
  • 10
  • For Chrome, `new PropertyCondition( AutomationElement.ControlTypeProperty, ControlType.TabItem )` seems to list all tabs, which are the children you would be interested in after finding `elm1`. – Steven Jeuris Mar 28 '14 at 18:07
  • 3
    However, seemingly only the currently active tab can be found using this method. :-( – Steven Jeuris Mar 30 '14 at 00:32
2

Maybe this code can help something; Thanks to BLEZ for share this code. I use this code to capture unique addresses from firefox and add them to a listbox. But I think this is not for Chrome right?

(you Should add NDde.dll to your project, to do this go to solution explorer right click to References-> add Reference->Browse-> find that DLL (http://ndde.codeplex.com/ from binary folder.))

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using NDde.Client;

namespace WindowsFormsApplication9
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            timer1.Enabled = true;
        }

        private string GetBrowserURL(string browser)
        {
            try
            {
                DdeClient dde = new DdeClient(browser, "WWW_GetWindowInfo");
                dde.Connect();
                string url = dde.Request("URL", int.MaxValue);
                string[] text = url.Split(new string[] { "\",\"" }, StringSplitOptions.RemoveEmptyEntries);
                dde.Disconnect();
                return text[0].Substring(1);
            }
            catch
            {
                return null;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            int j=0;
            for (int i = 0; i < listBox1.Items.Count; i++)
            {
                if (listBox1.Items[i].ToString() == GetBrowserURL("Firefox"))
                {
                    break;
                }
                else
                {
                    j++;
                }
            }
            if (j == listBox1.Items.Count)
            {
                listBox1.Items.Add(GetBrowserURL("Firefox"));
            }
        }
    }
}
MX D
  • 2,453
  • 4
  • 35
  • 47
matasoy
  • 21
  • 1
0

Below code work pretty well with Chrome Version 58.0.3029.110:

Please add reference of UIAutomationClient and UIAutomationProvider from Assembly provided by .NET.

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