Runtime two way IAccessible2 to UI Automation elements look up via unique id

Assistive technologies (ATs) who currently rely on IAccessible2 (IA2) that want to take advantage of the UI Automation (UIA) features at runtime can convert an IA2 element to an UIA element via a unique id and directly access UIA's API. This enables ATs who want to gradually transition from IA2 to UIA to experiment with individual UIA elements at runtime without switching entirely to UIA.

To look up an UIA element through a unique id, an AT can utilize IUIAutomationItemContainerPattern::FindItemByProperty() with the custom UIA unique id property and the element's unique id as parameters. To look up an IA2 element from an UIA element, an AT can simply utilize IUIAutomationLegacyIAccessiblePattern::GetIAccessible() and then query for IAccessible2 interface. The unique id is not needed to look up the IA2 element from UIA element.

Convert an IA2 element to UIA element via unique id

An IA2 element can be converted to an UIA element at runtime via a unique id that is shared between the two APIs.

Note: For the purpose of brevity and clarity, the code snippets below do not include clean-up of COM references neither does it have error handling.

  #include <uiautomation.h>
  #include <uiautomationclient.h>

  // Consider the following HTML:
  // <html>
  //  <button>button</button>
  // </html>

  // Register custom UIA property for retrieving the unique id of IA2 object.
  // {cc7eeb32-4b62-4f4c-aff6-1c2e5752ad8e}
  GUID UiaPropertyUniqueIdGuid = {
      0xcc7eeb32,
      0x4b62,
      0x4f4c,
      {0xaf, 0xf6, 0x1c, 0x2e, 0x57, 0x52, 0xad, 0x8e}};

  // Create the registrar object and get the IUIAutomationRegistrar
  // interface pointer.
  IUIAutomationRegistrar* registrar;
  CoCreateInstance(CLSID_CUIAutomationRegistrar, nullptr, CLSCTX_INPROC_SERVER,
                   IID_PPV_ARGS(&registrar));

  // Custom UIA property id used to retrieve the unique id between UIA/IA2.
  PROPERTYID uia_unique_id_property_id;

  // Register the custom UIA property that represents the unique id of an UIA
  // element which also matches its corresponding IA2 element's unique id.
  // Custom property registration only needs to be done once per process
  // lifetime.
  UIAutomationPropertyInfo unique_id_property_info = {
      UiaPropertyUniqueIdGuid, L"UniqueId", UIAutomationType_String};
  registrar->RegisterProperty(&unique_id_property_info,
                              &uia_unique_id_property_id);

  // Assume we are given the IAccessible2 element for button, and we want to
  // retrieve its corresponding UIA element for the final result.
  IAccessible2* button_ia2; /* Initialized */

  // Retrieve button IA2 element's unique id, which will be used to look up the
  // corresponding UIA element later.
  LONG unique_id_long;
  button_ia2->get_uniqueID(&unique_id_long);

  // Assume we are given the Window Handle hwnd for the root.
  UIA_HWND hwnd; /* Initialized */

  // Instantiating an IUIAutomation object.
  IUIAutomation* ui_automation;
  CoCreateInstance(CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER,
                   IID_PPV_ARGS(&ui_automation));

  // Retrieve the root element from the window handle.
  IUIAutomationElement* root_element;
  ui_automation->ElementFromHandle(hwnd, &root_element);

  // Retrieve the ItemContainerPattern of the root element.
  IUIAutomationItemContainerPattern* item_container_pattern;
  root_element->GetCurrentPatternAs(UIA_ItemContainerPatternId,
                                    IID_PPV_ARGS(&item_container_pattern));

  // We also need to convert the retrieved IA2 element unique id from long to
  // VARIANT.VT_BSTR to be consumed by UIA. For demo purpose, I utilize
  // std::string here as an intermediary step to convert to VARIANT.VT_BSTR.
  std::wstring unique_id_str = std::to_wstring(unique_id_long);

  VARIANT unique_id_variant;
  unique_id_variant.vt = VT_BSTR;
  unique_id_variant.bstrVal = SysAllocString(unique_id_str.c_str());

  // Retrieving the corresponding UIAutomation element from the unique id of IA2
  // object.
  IUIAutomationElement* button_uia;
  item_container_pattern->FindItemByProperty(nullptr, uia_unique_id_property_id,
                                             unique_id_variant,
                                             &button_uia /* final result */);

Convert an UIA element to IA2 element.

Converting an UIA element to an IA2 element is a lot more straightforward and does not require the shared unique id. Consider the same example above.

  #include <uiautomationclient.h>

  // Assume we are given the UIAutomation element for button, and we want to
  // retrieve its corresponding IAccessible2 element for the final result.
  IUIAutomationElement* button_uia; /* Initialized */

  // Retrieve the LegacyIAccessiblePattern of the button UIA element.
  IUIAutomationLegacyIAccessiblePattern* legacy_iaccessible_pattern;
  button_uia->GetCurrentPatternAs(
      UIA_LegacyIAccessiblePatternId,
      IID_PPV_ARGS(&legacy_iaccessible_pattern));

  // Retrieve the IAccessible element from button UIA element.
  // Note: According to UIA doc, GetIAccessible returns NULL if a client
  // attempts to retrieve the IAccessible interface for an element originally
  // supported by a proxy object from OLEACC.dll, or by the UIA-to-MSAA Bridge.
  // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationlegacyiaccessiblepattern-getiaccessible
  IAccessible* button_iaccessible;
  legacy_iaccessible_pattern->GetIAccessible(&button_iaccessible);

  // Use QueryService to retrieve button's IAccessible2 element from IAccessible
  // element.
  IServiceProvider* service_provider;
  IAccessible2* button_ia2;
  if (SUCCEEDED(button_iaccessible->QueryInterface(
          IID_PPV_ARGS(&service_provider)))) {
    service_provider->QueryService(
        IID_IAccessible, IID_PPV_ARGS(&button_ia2 /* final result */));
  }

Docs & References:

Custom UIA Property and Pattern registration in Chromium

UI Automation IItemContainerPattern. It is used to look up IAccessible2 element via a unique id

UI Automation Client