Microsoft Edge: Get Window URL and Title

2020-02-01 02:49发布

问题:

Previously I was using ShellWindows() API for IE to get the window Title and URL for my application Now with new development, Microsoft Edge is new and has many features under development.

I want to know how I can get the URL and Title of all the pages opened in MS-Edge. As none of the shell APIs are working with MS-Edge. I tried also with UI Automation but it does not return all the UI elements

I am using MS Visual Studio 2010 for development. Do I need new version of Visual Studio? Can anybody help me about how to access the Title and URL? Or Does MS-Edge not allow such an access due to security? thanks

回答1:

I'm not familiar with what's possible through Shell APIs here, but I just tried a test with UIA. I wrote the code below to access the title, url and window handle, and it seemed to work ok. I ran the code while Edge had a few tabs showing, and the page presented was Bing.com. This is the data found by the test...

The C# code uses a UIA interop dll that I generated through the tlbimp tool.

The test code makes a few assumptions which might need tightening up, but overall it looks like it can get the data you need. If you find this code doesn't work for you, if you let me know exactly which elements are involved, I can look into it.

Thanks,

Guy

IUIAutomationElement rootElement = uiAutomation.GetRootElement();

int propertyName = 30005; // UIA_NamePropertyId
int propertyAutomationId = 30011; // UIA_AutomationIdPropertyId
int propertyClassName = 30012; // UIA_ClassNamePropertyId
int propertyNativeWindowHandle = 30020; // UIA_NativeWindowHandlePropertyId

// Get the main Edge element, which is a direct child of the UIA root element.
// For this test, assume that the Edge element is the only element with an
// AutomationId of "TitleBar".
string edgeAutomationId = "TitleBar";

IUIAutomationCondition condition = 
    uiAutomation.CreatePropertyCondition(
        propertyAutomationId, edgeAutomationId);

// Have the window handle cached when we find the main Edge element.
IUIAutomationCacheRequest cacheRequestNativeWindowHandle = uiAutomation.CreateCacheRequest();
cacheRequestNativeWindowHandle.AddProperty(propertyNativeWindowHandle);

IUIAutomationElement edgeElement = 
    rootElement.FindFirstBuildCache(
        TreeScope.TreeScope_Children, 
        condition,
        cacheRequestNativeWindowHandle);

if (edgeElement != null)
{
    IntPtr edgeWindowHandle = edgeElement.CachedNativeWindowHandle;

    // Next find the element whose name is the url of the loaded page. And have
    // the name of the element related to the url cached when we find the element.
    IUIAutomationCacheRequest cacheRequest =
        uiAutomation.CreateCacheRequest();
    cacheRequest.AddProperty(propertyName);

    // For this test, assume that the element with the url is the first descendant element
    // with a ClassName of "Internet Explorer_Server".
    string urlElementClassName = "Internet Explorer_Server";

    IUIAutomationCondition conditionUrl =
        uiAutomation.CreatePropertyCondition(
            propertyClassName,
            urlElementClassName);

    IUIAutomationElement urlElement =
        edgeElement.FindFirstBuildCache(
            TreeScope.TreeScope_Descendants,
            conditionUrl,
            cacheRequest);

    string url = urlElement.CachedName;

    // Next find the title of the loaded page. First find the list of 
    // tabs shown at the top of Edge.
    string tabsListAutomationId = "TabsList";

    IUIAutomationCondition conditionTabsList =
        uiAutomation.CreatePropertyCondition(
            propertyAutomationId, tabsListAutomationId);

    IUIAutomationElement tabsListElement =
        edgeElement.FindFirst(
            TreeScope.TreeScope_Descendants,
            conditionTabsList);

    // Find which of those tabs is selected. (It should be possible to 
    // cache the Selection pattern with the above call, and that would
    // avoid one cross-process call here.)
    int selectionPatternId = 10001; // UIA_SelectionPatternId
    IUIAutomationSelectionPattern selectionPattern = 
        tabsListElement.GetCurrentPattern(selectionPatternId);

    // For this test, assume there's always one selected item in the list.
    IUIAutomationElementArray elementArray = selectionPattern.GetCurrentSelection();
    string title = elementArray.GetElement(0).CurrentName;

    // Now show the title, url and window handle.
    MessageBox.Show(
        "Page title: " + title +
        "\r\nURL: " + url + 
        "\r\nhwnd: " + edgeWindowHandle);
}


回答2:

I've just pointed the Inspect SDK tool to Edge, when Edge showed 3 tabs. (Inspect uses the UIA Client API to access all the UI shown in apps, and if you have the SDK installed, can be found in places like "C:\Program Files (x86)\Windows Kits\10\bin\x64".) The image below shows that the UIA element for the main Edge window has a child element which is a list. Inspect also shows me that the AutomationId of that child element is "TabsList", (but that's not shown in the image below). The set of list items that are direct children of the TabsList, have names which are the titles of the pages loaded in Edge. So it looks like it's relatively straightforward to access the titles of the pages loaded in Edge.

However, it looks like it's not so clean to access the URL associated with a page. When I looked through what Inspect was showing me, I only found the URL in the UIA tree for the page that's currently being shown. The URL can be found from the name of an element with a control type of Pane, which is beneath a chain of Pane elements. I don't know what the most robust way of reaching the element with the URL is. For example, if it's the only element with a UIA ClassName of "InternetExplorer_Server", or its parent is the only element with a ClassName of "TabWindowClass", then maybe a single call to FindFirstBuildCache() could get you close to the element you're interested in. (It looks like neither element has an AutomationId to search for.)

Thanks,

Guy



回答3:

This example project monitors title and url for Microsoft Edge active window and active tab. Tested on Windows 10:

  • 10240 Pro
  • 10586 Home
  • 14939 Pro

C#

class Program
{
    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    static bool TryGetMSEdgeUrlAndTitle(IntPtr edgeWindow, out string url, out string title)
    {
        const int UIA_NamePropertyId = 30005;
        const int UIA_ClassNamePropertyId = 30012;
        const int UIA_NativeWindowHandlePropertyId = 30020;

        url = "";
        title = "";

        IUIAutomation uiA = new CUIAutomation();
        IUIAutomationElement rootElement = uiA.GetRootElement();

        IUIAutomationCacheRequest cacheRequest = uiA.CreateCacheRequest();
        cacheRequest.AddProperty(UIA_NamePropertyId);

        IUIAutomationCondition windowCondition = uiA.CreatePropertyCondition(UIA_NativeWindowHandlePropertyId, GetForegroundWindow());
        IUIAutomationElement windowElement = rootElement.FindFirstBuildCache(TreeScope.TreeScope_Descendants, windowCondition, cacheRequest);
        if (windowElement == null)
            return false;

        IUIAutomationCondition edgeCondition = uiA.CreatePropertyCondition(UIA_NamePropertyId, "Microsoft Edge");
        IUIAutomationElement edgeElement = windowElement.FindFirstBuildCache(TreeScope.TreeScope_Subtree, edgeCondition, cacheRequest);
        if (edgeElement == null)
            return false;

        IUIAutomationCondition tabCondition = uiA.CreatePropertyCondition(UIA_ClassNamePropertyId, "TabWindowClass");
        IUIAutomationElement tabElement = edgeElement.FindFirstBuildCache(TreeScope.TreeScope_Descendants, tabCondition, cacheRequest);
        if (tabElement == null)
            return false;

        IUIAutomationCondition ieCondition = uiA.CreatePropertyCondition(UIA_ClassNamePropertyId, "Internet Explorer_Server");
        IUIAutomationElement ieElement = tabElement.FindFirstBuildCache(TreeScope.TreeScope_Descendants, ieCondition, cacheRequest);
        if (ieElement == null)
            return false;

        url = ieElement.CachedName;
        title = tabElement.CachedName;

        return true;
    }

    static void Main(string[] args)
    {
        string oldUrl = "";
        string oldTitle = "";

        while (true)
        {
            string url = "";
            string title = "";

            if (TryGetMSEdgeUrlAndTitle(GetForegroundWindow(), out url, out title))
            {
                if ((url != oldUrl) || (title != oldTitle))
                {
                    Console.WriteLine(String.Format("Page title: {0} \r\nURL: {1}", title, url));

                    oldUrl = url;
                    oldTitle = title;
                }
            }

            Thread.Sleep(250);
        }
    }
}


回答4:

In response to the comments from Yves above...

I only use the Windows UIA API these days. While the .NET UIA API is perfectly fine to use for some things, I believe the Windows UIA API has had more investment in recent years. For example, the Windows UIA has been updated to be more resilient when it encounters unresponsive UIA providers, (eg suspended processes). Also, some Windows UIA interfaces now have '2' versions, and I doubt all those interfaces exist in the .NET UIA API. So for me, I use tlbimp.exe to generate a wrapper around the Windows UIA API, and always use that.

Regarding getting the content of the addressEdit Box, that should be accessible through the UIA Value pattern. Typically edit controls have a UIA Name property that describes the purpose of the edit control, and the content of the control is exposed through the Value pattern.

The screenshot below shows the Inspect SDK tool reporting the properties exposed through the Value pattern. (The screenshot was taken on a machine with no net connection, hence the "You're not connected" tab names.) Inspect shows me that the Value pattern's Value property is "microsoft.com" and the IsReadOnly property is false.

I don't know how well the .NET UIA API behaves with Edge UI, but I expect a client of the Windows UIA API will be able to access the edit control text fine.

Thanks,

Guy



回答5:

I have tried Guy Barker above code in Windows 10 Professional machine, its working great.

If I have tried the same code in Windows 10 Home Edition(upgraded from windows 8.1) machine, its not working and the"urlElement" return null for me. The code not find the Internet Explorer_Server class. But the Internet Explorer_Server class has found while navigate using inspect.exe.

IUIAutomationElement urlElement =
        edgeElement.FindFirstBuildCache(
            TreeScope.TreeScope_Descendants,
            conditionUrl,
            cacheRequest);

if(urlElement == null)//true

I have explore further, the code not capture pane(Spartan XAML-To-Trident Input Routing Window) node in windows 10 Home edition machine. So i could not reach "Internet Explorer_Server" class to find URL.

Is any difference between home and professional edition OS? How to solve it?

Thanks

Satheesh



回答6:

I use VB.Net version, Windows 10 Home OS. Works for me. I get the page title and page URL. Code is part of one of my modules. Please copy and edit it as needed.

'-----------------------------------------------------------------------------
'Allow code to get Microsoft Edge URL & Title
'   Add .Net references for UIAutomationClient & UIAutomationTypes
Imports System.Windows.Automation
'-----------------------------------------------------------------------------

Public Function ActiveMicrosoftEdgeTitleAndURL(ByRef HadError As Boolean,
                                               ByVal InhibitMsgBox As Boolean) As String()

    Dim i1 As Integer
    Dim tmp1 As String = "", tmp2() As String, METitle As String, MEURL As String
    Dim strME As String = "Microsoft Edge"

    'ActiveMicrosoftEdgeTitleAndURL(Index) = Page Title or "No Title" + Chr(255) + Page URL

    'If no Page URL then any Page Title is ignored.
    '   If the form is minimized to the taskbar the url is typically not available.

    HadError = False : ReDim tmp2(-1) : i1 = -1

    Try
        Dim conditions As Condition = Condition.TrueCondition
        Dim BaseElement As AutomationElement = AutomationElement.RootElement
        Dim elementCollection As AutomationElementCollection = BaseElement.FindAll(TreeScope.Children, conditions)
        Dim AE As AutomationElement
        For Each AE In elementCollection
            If AE IsNot Nothing Then
                tmp1 = AE.GetCurrentPropertyValue(AutomationElement.NameProperty).ToString
                If StrComp(Strings.Right(tmp1, strME.Length), strME, vbTextCompare) = 0 Then
                    MEURL = "" : METitle = ""
                    '-----------------------------------------------------------------------------------------------------------
                    Dim AE1 As AutomationElement = _
                        AE.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.AutomationIdProperty, "TitleBar"))
                    METitle = AutomationElementText(AE1)
                    METitle = Trim(METitle)
                    '-----------------------------------------------------------------------------------------------------------
                    AE1 = AE.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.AutomationIdProperty, "addressEditBox"))
                    MEURL = AutomationElementText(AE1)
                    MEURL = Trim(MEURL)
                    '-----------------------------------------------------------------------------------------------------------
                    If MEURL <> "" Then
                        If METitle = "" Then METitle = "No Title"
                        i1 = i1 + 1 : Array.Resize(tmp2, i1 + 1)
                        tmp2(i1) = METitle + Chr(255) + MEURL
                    End If
                End If
            End If
        Next
    Catch ex As Exception
        HadError = True
        MsgBox("Function AutomationElementData system error." + vbCr + vbCr + ex.ToString, vbExclamation)
    End Try

    Return tmp2

End Function

Private Function AutomationElementText(ByRef AE As AutomationElement) As String

    Dim MyPattern As AutomationPattern = ValuePattern.Pattern
    Dim MyPattern1 As AutomationPattern = TextPattern.Pattern
    Dim objPattern As Object = Nothing
    Dim txt As String = ""

    'Any error just return a null string. !r

    If AE.TryGetCurrentPattern(MyPattern, objPattern) Then
        Dim AEValuePattern As ValuePattern = AE.GetCurrentPattern(MyPattern)
        txt = AEValuePattern.Current.Value
    Else
        If AE.TryGetCurrentPattern(MyPattern1, objPattern) Then
            Dim AETextPattern As TextPattern = AE.GetCurrentPattern(MyPattern1)
            txt = AETextPattern.DocumentRange.GetText(-1)
        End If
    End If

    Return txt

End Function


回答7:

Dim tmp1 As String = "" Dim strME As String = "Microsoft Edge"

    Try
        Dim conditions As Condition = Condition.TrueCondition
        Dim BaseElement As AutomationElement = AutomationElement.RootElement
        Dim elementCollection As AutomationElementCollection = BaseElement.FindAll(TreeScope.Children, conditions)
        Dim AE As AutomationElement

        For Each AE In elementCollection
            If AE IsNot Nothing Then
                tmp1 = AE.GetCurrentPropertyValue(AutomationElement.NameProperty).ToString
                If StrComp(Strings.Right(tmp1, strME.Length), strME, vbTextCompare) = 0 Then
                    Dim elmUrlBar0 As AutomationElement
                    elmUrlBar0 = AE.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.AutomationIdProperty, "addressEditBox"))
                    If elmUrlBar0 IsNot Nothing Then
                        Dim patterns0 As AutomationPattern() = elmUrlBar0.GetSupportedPatterns()
                        If patterns0.Length > 0 Then
                            Dim val As ValuePattern = DirectCast(elmUrlBar0.GetCurrentPattern(patterns0(0)), ValuePattern)
                            If Not elmUrlBar0.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty) Then
                                Dim pvgc As String = LCase(val.Current.Value).Trim
                                If pvgc.ToString <> "" Then
                                    urls = pvgc.ToString
                                    MsgBox(urls)
                                End If
                            End If
                        End If
                    End If
                End If
            End If
        Next

    Catch ex As Exception
        MsgBox("Function AutomationElementData system error." + vbCr + vbCr + ex.ToString, vbExclamation)
    End Try