chromium/ui/accessibility/platform/inspect/ax_tree_formatter_uia_win.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/accessibility/platform/inspect/ax_tree_formatter_uia_win.h"

#include <math.h>
#include <oleacc.h>
#include <stddef.h>
#include <stdint.h>
#include <wrl/client.h>

#include <iostream>
#include <memory>
#include <string>
#include <utility>

#include "base/files/file_path.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "base/win/com_init_util.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/ax_platform_tree_manager.h"
#include "ui/accessibility/platform/inspect/ax_inspect_utils_win.h"
#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/gfx/win/hwnd_util.h"

#include <uiautomation.h>

namespace {

std::string UiaIdentifierToCondensedString(int32_t id) {
  std::string identifier = ui::UiaIdentifierToStringUTF8(id);
  if (id >= UIA_RuntimeIdPropertyId && id <= UIA_HeadingLevelPropertyId) {
    // remove leading 'UIA_' and trailing 'PropertyId'
    return identifier.substr(4, identifier.size() - 14);
  }
  if (id >= UIA_ButtonControlTypeId && id <= UIA_AppBarControlTypeId) {
    // remove leading 'UIA_' and trailing 'ControlTypeId'
    return identifier.substr(4, identifier.size() - 17);
  }
  return identifier;
}

// The RuntimeId returned from IUIAutomationElement is different than the one
// we hand out in IRawElementProviderFragment::GetRuntimeId, as UIA modifies it.
// This function takes an existing IUIAutomationElement and swaps in the
// Chromium specific bits of the internal runtime id so that the returned
// SAFEARRAY can be used in IUIAutomationElement-based search/conditionals.
void GetUIARuntimeId(IUIAutomationElement* first_child,
                     IRawElementProviderFragment* start_fragment,
                     SAFEARRAY** runtime_id_out) {
  DCHECK(first_child && start_fragment && runtime_id_out);
  std::array<int, 3> internal_id;
  {
    // Get the internal runtime id from the IRawElementProviderFragment based
    // GetRuntimeId.
    base::win::ScopedSafearray start_fragment_runtime_id;
    start_fragment->GetRuntimeId(start_fragment_runtime_id.Receive());
    LONG lower_bound = 0;
    HRESULT hr =
        ::SafeArrayGetLBound(start_fragment_runtime_id.Get(), 1, &lower_bound);
    CHECK(SUCCEEDED(hr));
    LONG upper_bound = 0;
    hr = ::SafeArrayGetUBound(start_fragment_runtime_id.Get(), 1, &upper_bound);
    CHECK(SUCCEEDED(hr));
    CHECK(lower_bound >= 0);
    LONG fragment_id_length = (upper_bound - lower_bound) + 1;
    CHECK(fragment_id_length == 4);

    int32_t* fragment_id_array = nullptr;
    ::SafeArrayAccessData(start_fragment_runtime_id.Get(),
                          reinterpret_cast<void**>(&fragment_id_array));
    CHECK(fragment_id_array);
    // Grab out the last three ints from the internal runtime id. This should
    // correspond with the frame tree id and DOM id.
    internal_id = {fragment_id_array[1], fragment_id_array[2],
                   fragment_id_array[3]};

    ::SafeArrayUnaccessData(start_fragment_runtime_id.Get());
  }

  base::win::ScopedSafearray runtime_id;
  first_child->GetRuntimeId(runtime_id.Receive());
  CHECK(runtime_id.Get());
  LONG lower_bound = 0;
  HRESULT hr = ::SafeArrayGetLBound(runtime_id.Get(), 1, &lower_bound);
  CHECK(SUCCEEDED(hr));
  LONG upper_bound = 0;
  hr = ::SafeArrayGetUBound(runtime_id.Get(), 1, &upper_bound);
  CHECK(SUCCEEDED(hr));
  LONG runtime_id_length = upper_bound - lower_bound + 1;
  CHECK(runtime_id_length >= 4);
  {
    int32_t* runtime_id_array = nullptr;
    ::SafeArrayAccessData(runtime_id.Get(),
                          reinterpret_cast<void**>(&runtime_id_array));
    CHECK(runtime_id_array);

    // Stuff the internal id values in the last three spots in the grabbed
    // UIA-based runtime id.
    runtime_id_array[upper_bound - 2] = internal_id[0];
    runtime_id_array[upper_bound - 1] = internal_id[1];
    runtime_id_array[upper_bound] = internal_id[2];
    ::SafeArrayUnaccessData(runtime_id.Get());
  }

  *runtime_id_out = runtime_id.Release();
}

void GetUIARoot(ui::AXPlatformNodeDelegate* start,
                IUIAutomation* uia,
                IUIAutomationElement** root) {
  // If dumping when the page or iframe is reloading, the
  // tree manager may have been removed.
  ui::AXTreeManager* tree_manager = start->GetTreeManager();
  if (!tree_manager) {
    return;
  }

  // Start by getting the root element for the HWND hosting the web content.
  HWND hwnd = static_cast<ui::AXPlatformTreeManager*>(tree_manager)
                  ->RootDelegate()
                  ->GetTargetForNativeAccessibilityEvent();
  uia->ElementFromHandle(hwnd, root);
}

void GetUIAElementFromDelegate(ui::AXPlatformNodeDelegate* start,
                               IUIAutomation* uia,
                               IUIAutomationElement** element) {
  // We use the UI Automation client API to produce the tree dump, but
  // BrowserAccessibility has a pointer to a provider API implementation, and
  // we can't directly relate the two -- the OS manages the relationship.
  // To locate the client element we want, we'll construct a RuntimeId
  // corresponding to our provider element, then search for that.
  Microsoft::WRL::ComPtr<IUIAutomationElement> root;
  // If dumping when the page or iframe is reloading, we may encounter
  // a brief moment when the root cannot be found through the tree_manager.
  GetUIARoot(start, uia, &root);
  if (!root.Get()) {
    return;
  }

  // The root element is provided by AXFragmentRootWin, whose RuntimeId is not
  // in the same form as elements provided by BrowserAccessibility.
  // Find the root element's first child, which should be provided by
  // BrowserAccessibility. We'll use that element's RuntimeId as a template for
  // the RuntimeId of the element we're looking for.
  Microsoft::WRL::ComPtr<IUIAutomationTreeWalker> tree_walker;
  uia->get_RawViewWalker(&tree_walker);
  Microsoft::WRL::ComPtr<IUIAutomationElement> first_child;
  tree_walker->GetFirstChildElement(root.Get(), &first_child);
  CHECK(first_child.Get());

  // Get first_child's RuntimeId and swap out the last element in its SAFEARRAY
  // for the UniqueId of the element we want to start from.
  Microsoft::WRL::ComPtr<IUnknown> start_unknown =
      start->GetNativeViewAccessible();
  Microsoft::WRL::ComPtr<IRawElementProviderFragment> start_fragment;
  start_unknown.As(&start_fragment);
  CHECK(start_fragment.Get());
  base::win::ScopedSafearray uia_runtime_id;
  GetUIARuntimeId(first_child.Get(), start_fragment.Get(),
                  uia_runtime_id.Receive());

  // Find the element with the desired RuntimeId.
  base::win::ScopedVariant runtime_id_variant(uia_runtime_id.Release());
  Microsoft::WRL::ComPtr<IUIAutomationCondition> condition;
  uia->CreatePropertyCondition(UIA_RuntimeIdPropertyId, runtime_id_variant,
                               &condition);
  CHECK(condition);

  root->FindFirst(TreeScope_Subtree, condition.Get(), element);
}

RECT GetUIARootBounds(ui::AXPlatformNodeDelegate* delegate,
                      IUIAutomation* uia) {
  Microsoft::WRL::ComPtr<IUIAutomationElement> root;
  GetUIARoot(delegate, uia, &root);
  CHECK(root.Get());
  RECT root_bounds = {0};
  root->get_CurrentBoundingRectangle(&root_bounds);

  return root_bounds;
}

}  // namespace

namespace ui {

// This is the list of interesting properties to dump.
//
// Certain properties are skipped because they are known to cause crashes if the
// underlying pattern isn't implemented (e.g., LegacyIAccessibleSelection will
// crash on Win7 if the LegacyIAccessible pattern isn't implemented).
//
// Other properties aren't interesting in a tree-dump context (e.g., ProcessId).
//
// Finally, certain properties are dumped as part of a pattern, and don't need
// to be dumped a second time here (e.g., Grid*, GridItem*, RangeValue*, etc.).

// static
const long AXTreeFormatterUia::properties_[] = {
    // UIA_RuntimeIdPropertyId                          // 30000
    UIA_BoundingRectanglePropertyId,  // 30001
    // UIA_ProcessIdPropertyId                          // 30002
    UIA_ControlTypePropertyId,           // 30003
    UIA_LocalizedControlTypePropertyId,  // 30004
    UIA_NamePropertyId,                  // 30005
    UIA_AcceleratorKeyPropertyId,        // 30006
    UIA_AccessKeyPropertyId,             // 30007
    UIA_HasKeyboardFocusPropertyId,      // 30008
    UIA_IsKeyboardFocusablePropertyId,   // 30009
    UIA_IsEnabledPropertyId,             // 30010
    UIA_AutomationIdPropertyId,          // 30011
    UIA_ClassNamePropertyId,             // 30012
    UIA_HelpTextPropertyId,              // 30013
    UIA_ClickablePointPropertyId,        // 30014
    UIA_CulturePropertyId,               // 30015
    UIA_IsControlElementPropertyId,      // 30016
    UIA_IsContentElementPropertyId,      // 30017
    UIA_LabeledByPropertyId,             // 30018
    UIA_IsPasswordPropertyId,            // 30019
    // UIA_NativeWindowHandlePropertyId                 // 30020
    UIA_ItemTypePropertyId,                          // 30021
    UIA_IsOffscreenPropertyId,                       // 30022
    UIA_OrientationPropertyId,                       // 30023
    UIA_FrameworkIdPropertyId,                       // 30024
    UIA_IsRequiredForFormPropertyId,                 // 30025
    UIA_ItemStatusPropertyId,                        // 30026
    UIA_IsDockPatternAvailablePropertyId,            // 30027
    UIA_IsExpandCollapsePatternAvailablePropertyId,  // 30028
    UIA_IsGridItemPatternAvailablePropertyId,        // 30029
    UIA_IsGridPatternAvailablePropertyId,            // 30030
    UIA_IsInvokePatternAvailablePropertyId,          // 30031
    UIA_IsMultipleViewPatternAvailablePropertyId,    // 30032
    UIA_IsRangeValuePatternAvailablePropertyId,      // 30033
    UIA_IsScrollPatternAvailablePropertyId,          // 30034
    UIA_IsScrollItemPatternAvailablePropertyId,      // 30035
    UIA_IsSelectionItemPatternAvailablePropertyId,   // 30036
    UIA_IsSelectionPatternAvailablePropertyId,       // 30037
    UIA_IsTablePatternAvailablePropertyId,           // 30038
    UIA_IsTableItemPatternAvailablePropertyId,       // 30039
    UIA_IsTextPatternAvailablePropertyId,            // 30040
    UIA_IsTogglePatternAvailablePropertyId,          // 30041
    UIA_IsTransformPatternAvailablePropertyId,       // 30042
    UIA_IsValuePatternAvailablePropertyId,           // 30043
    UIA_IsWindowPatternAvailablePropertyId,          // 30044
    // UIA_Value*                                       // 30045-30046
    // UIA_RangeValue*                                  // 30047-30052
    // UIA_Scroll*                                      // 30053-30058
    // UIA_Selection*                                   // 30059-30061
    // UIA_Grid*                                        // 30062-30068
    // UIA_DockDockPositionPropertyId,                  // 30069
    // UIA_ExpandCollapseExpandCollapseStatePropertyId, // 30070
    // UIA_MultipleViewCurrentViewPropertyId,           // 30071
    // UIA_MultipleViewSupportedViewsPropertyId,        // 30072
    // UIA_WindowCanMaximizePropertyId,                 // 30073
    // UIA_WindowCanMinimizePropertyId,                 // 30074
    // UIA_WindowWindowVisualStatePropertyId,           // 30075
    // UIA_WindowWindowInteractionStatePropertyId,      // 30076
    // UIA_WindowIsModalPropertyId                      // 30077
    // UIA_WindowIsTopmostPropertyId,                   // 30078
    // UIA_SelectionItem*                               // 30079-30080
    // UIA_TableRowHeadersPropertyId,                   // 30081
    // UIA_TableColumnHeadersPropertyId,                // 30082
    // UIA_TableRowOrColumnMajorPropertyId              // 30083
    // UIA_TableItemRowHeaderItemsPropertyId,           // 30084
    // UIA_TableItemColumnHeaderItemsPropertyId,        // 30085
    // UIA_ToggleToggleStatePropertyId                  // 30086
    // UIA_TransformCanMovePropertyId,                  // 30087
    // UIA_TransformCanResizePropertyId,                // 30088
    // UIA_TransformCanRotatePropertyId,                // 30089
    UIA_IsLegacyIAccessiblePatternAvailablePropertyId,  // 30090
    // UIA_LegacyIAccessible*                           // 30091-30100
    UIA_AriaRolePropertyId,            // 30101
    UIA_AriaPropertiesPropertyId,      // 30102
    UIA_IsDataValidForFormPropertyId,  // 30103
    UIA_ControllerForPropertyId,       // 30104
    UIA_DescribedByPropertyId,         // 30105
    UIA_FlowsToPropertyId,             // 30106
    // UIA_ProviderDescriptionPropertyId                // 30107
    UIA_IsItemContainerPatternAvailablePropertyId,      // 30108
    UIA_IsVirtualizedItemPatternAvailablePropertyId,    // 30109
    UIA_IsSynchronizedInputPatternAvailablePropertyId,  // 30110
    UIA_OptimizeForVisualContentPropertyId,             // 30111
    UIA_IsObjectModelPatternAvailablePropertyId,        // 30112
    UIA_AnnotationAnnotationTypeIdPropertyId,           // 30113
    UIA_AnnotationAnnotationTypeNamePropertyId,         // 30114
    UIA_AnnotationAuthorPropertyId,                     // 30115
    UIA_AnnotationDateTimePropertyId,                   // 30116
    UIA_AnnotationTargetPropertyId,                     // 30117
    UIA_IsAnnotationPatternAvailablePropertyId,         // 30118
    UIA_IsTextPattern2AvailablePropertyId,              // 30119
    UIA_StylesStyleIdPropertyId,                        // 30120
    UIA_StylesStyleNamePropertyId,                      // 30121
    UIA_StylesFillColorPropertyId,                      // 30122
    UIA_StylesFillPatternStylePropertyId,               // 30123
    UIA_StylesShapePropertyId,                          // 30124
    UIA_StylesFillPatternColorPropertyId,               // 30125
    UIA_StylesExtendedPropertiesPropertyId,             // 30126
    UIA_IsStylesPatternAvailablePropertyId,             // 30127
    UIA_IsSpreadsheetPatternAvailablePropertyId,        // 30128
    UIA_SpreadsheetItemFormulaPropertyId,               // 30129
    UIA_SpreadsheetItemAnnotationObjectsPropertyId,     // 30130
    UIA_SpreadsheetItemAnnotationTypesPropertyId,       // 30131
    UIA_IsSpreadsheetItemPatternAvailablePropertyId,    // 30132
    UIA_Transform2CanZoomPropertyId,                    // 30133
    UIA_IsTransformPattern2AvailablePropertyId,         // 30134
    UIA_LiveSettingPropertyId,                          // 30135
    UIA_IsTextChildPatternAvailablePropertyId,          // 30136
    UIA_IsDragPatternAvailablePropertyId,               // 30137
    UIA_DragIsGrabbedPropertyId,                        // 30138
    UIA_DragDropEffectPropertyId,                       // 30139
    UIA_DragDropEffectsPropertyId,                      // 30140
    UIA_IsDropTargetPatternAvailablePropertyId,         // 30141
    UIA_DropTargetDropTargetEffectPropertyId,           // 30142
    UIA_DropTargetDropTargetEffectsPropertyId,          // 30143
    UIA_DragGrabbedItemsPropertyId,                     // 30144
    UIA_Transform2ZoomLevelPropertyId,                  // 30145
    UIA_Transform2ZoomMinimumPropertyId,                // 30146
    UIA_Transform2ZoomMaximumPropertyId,                // 30147
    UIA_FlowsFromPropertyId,                            // 30148
    UIA_IsTextEditPatternAvailablePropertyId,           // 30149
    UIA_IsPeripheralPropertyId,                         // 30150
    UIA_IsCustomNavigationPatternAvailablePropertyId,   // 30151
    UIA_PositionInSetPropertyId,                        // 30152
    UIA_SizeOfSetPropertyId,                            // 30153
    UIA_LevelPropertyId,                                // 30154
    UIA_AnnotationTypesPropertyId,                      // 30155
    UIA_AnnotationObjectsPropertyId,                    // 30156
    UIA_LandmarkTypePropertyId,                         // 30157
    UIA_LocalizedLandmarkTypePropertyId,                // 30158
    UIA_FullDescriptionPropertyId,                      // 30159
    UIA_FillColorPropertyId,                            // 30160
    UIA_OutlineColorPropertyId,                         // 30161
    UIA_FillTypePropertyId,                             // 30162
    UIA_VisualEffectsPropertyId,                        // 30163
    UIA_OutlineThicknessPropertyId,                     // 30164
    UIA_CenterPointPropertyId,                          // 30165
    UIA_RotationPropertyId,                             // 30166
    UIA_SizePropertyId,                                 // 30167
    UIA_IsSelectionPattern2AvailablePropertyId,         // 30168
    UIA_Selection2FirstSelectedItemPropertyId,          // 30169
    UIA_Selection2LastSelectedItemPropertyId,           // 30170
    UIA_Selection2CurrentSelectedItemPropertyId,        // 30171
    UIA_Selection2ItemCountPropertyId,                  // 30172
    UIA_HeadingLevelPropertyId,                         // 30173
};

const long AXTreeFormatterUia::patterns_[] = {
    UIA_SelectionPatternId,       // 10001
    UIA_ValuePatternId,           // 10002
    UIA_RangeValuePatternId,      // 10003
    UIA_ScrollPatternId,          // 10004
    UIA_ExpandCollapsePatternId,  // 10005
    UIA_GridPatternId,            // 10006
    UIA_GridItemPatternId,        // 10007
    UIA_WindowPatternId,          // 10009
    UIA_SelectionItemPatternId,   // 10010
    UIA_TablePatternId,           // 10012
    UIA_TogglePatternId,          // 10015
    UIA_AnnotationPatternId,      // 10023
};

const long AXTreeFormatterUia::pattern_properties_[] = {
    UIA_ValueValuePropertyId,                         // 30045
    UIA_ValueIsReadOnlyPropertyId,                    // 30046
    UIA_RangeValueValuePropertyId,                    // 30047
    UIA_RangeValueIsReadOnlyPropertyId,               // 30048
    UIA_RangeValueMinimumPropertyId,                  // 30049
    UIA_RangeValueMaximumPropertyId,                  // 30050
    UIA_RangeValueLargeChangePropertyId,              // 30051
    UIA_RangeValueSmallChangePropertyId,              // 30052
    UIA_ScrollHorizontalScrollPercentPropertyId,      // 30053
    UIA_ScrollHorizontalViewSizePropertyId,           // 30054
    UIA_ScrollVerticalScrollPercentPropertyId,        // 30055
    UIA_ScrollVerticalViewSizePropertyId,             // 30056
    UIA_ScrollHorizontallyScrollablePropertyId,       // 30057
    UIA_ScrollVerticallyScrollablePropertyId,         // 30058
    UIA_SelectionCanSelectMultiplePropertyId,         // 30060
    UIA_SelectionIsSelectionRequiredPropertyId,       // 30061
    UIA_GridRowCountPropertyId,                       // 30062
    UIA_GridColumnCountPropertyId,                    // 30063
    UIA_GridItemRowPropertyId,                        // 30064
    UIA_GridItemColumnPropertyId,                     // 30065
    UIA_GridItemRowSpanPropertyId,                    // 30066
    UIA_GridItemColumnSpanPropertyId,                 // 30067
    UIA_GridItemContainingGridPropertyId,             // 30068
    UIA_ExpandCollapseExpandCollapseStatePropertyId,  // 30070
    UIA_WindowIsModalPropertyId,                      // 30077
    UIA_SelectionItemIsSelectedPropertyId,            // 30079
    UIA_SelectionItemSelectionContainerPropertyId,    // 30080
    UIA_TableRowOrColumnMajorPropertyId,              // 30083
    UIA_ToggleToggleStatePropertyId,                  // 30086
};

AXTreeFormatterUia::AXTreeFormatterUia() {
  // Create an instance of the CUIAutomation class.
  CoCreateInstance(CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER,
                   IID_IUIAutomation, &uia_);
  CHECK(uia_.Get());
  BuildCacheRequests();
  BuildCustomPropertiesMap();
}

AXTreeFormatterUia::~AXTreeFormatterUia() {}

void AXTreeFormatterUia::AddDefaultFilters(
    std::vector<AXPropertyFilter>* property_filters) {
  // Too noisy: IsKeyboardFocusable, IsDataValidForForm, UIA_ScrollPatternId,
  //  Value.IsReadOnly

  // properties not exposed through a pattern
  AddPropertyFilter(property_filters, "Name=*");
  AddPropertyFilter(property_filters, "ItemStatus=*");
  AddPropertyFilter(property_filters, "Orientation=OrientationType_Horizontal");
  AddPropertyFilter(property_filters, "IsPassword=true");
  AddPropertyFilter(property_filters, "IsControlElement=false");
  AddPropertyFilter(property_filters, "IsEnabled=false");
  AddPropertyFilter(property_filters, "IsRequiredForForm=true");

  // UIA_ExpandCollapsePatternId
  AddPropertyFilter(property_filters, "ExpandCollapse.ExpandCollapseState=*");

  // UIA_GridPatternId
  AddPropertyFilter(property_filters, "Grid.ColumnCount=*");
  AddPropertyFilter(property_filters, "Grid.RowCount=*");
  // UIA_GridItemPatternId
  AddPropertyFilter(property_filters, "GridItem.Column=*");
  AddPropertyFilter(property_filters, "GridItem.ColumnSpan=*");
  AddPropertyFilter(property_filters, "GridItem.Row=*");
  AddPropertyFilter(property_filters, "GridItem.RowSpan=*");
  AddPropertyFilter(property_filters, "GridItem.ContainingGrid=*");
  // UIA_RangeValuePatternId
  AddPropertyFilter(property_filters, "RangeValue.IsReadOnly=*");
  AddPropertyFilter(property_filters, "RangeValue.LargeChange=*");
  AddPropertyFilter(property_filters, "RangeValue.SmallChange=*");
  AddPropertyFilter(property_filters, "RangeValue.Maximum=*");
  AddPropertyFilter(property_filters, "RangeValue.Minimum=*");
  AddPropertyFilter(property_filters, "RangeValue.Value=*");
  // UIA_SelectionPatternId
  AddPropertyFilter(property_filters, "Selection.CanSelectMultiple=*");
  AddPropertyFilter(property_filters, "Selection.IsSelectionRequired=*");
  // UIA_SelectionItemPatternId
  AddPropertyFilter(property_filters, "SelectionItem.IsSelected=*");
  AddPropertyFilter(property_filters, "SelectionItem.SelectionContainer=*");
  // UIA_TablePatternId
  AddPropertyFilter(property_filters, "Table.RowOrColumnMajor=*");
  // UIA_TogglePatternId
  AddPropertyFilter(property_filters, "Toggle.ToggleState=*");
  // UIA_ValuePatternId
  AddPropertyFilter(property_filters, "Value.Value=*");
  AddPropertyFilter(property_filters, "Value.Value='http*'",
                    AXPropertyFilter::DENY);
  // UIA_WindowPatternId
  AddPropertyFilter(property_filters, "Window.IsModal=*");

  // Custom properties.
  AddPropertyFilter(
      property_filters,
      GetPropertyName(
          UiaRegistrarWin::GetInstance().GetVirtualContentPropertyId()) +
          "=*");
}

base::Value::Dict AXTreeFormatterUia::BuildTree(
    AXPlatformNodeDelegate* start) const {
  Microsoft::WRL::ComPtr<IUIAutomationElement> start_element;
  GetUIAElementFromDelegate(start, uia_.Get(), &start_element);

  base::Value::Dict tree;
  if (!start_element) {
    return tree;
  }

  RECT root_bounds = GetUIARootBounds(start, uia_.Get());
  if (start_element.Get()) {
    // Build an accessibility tree starting from that element.
    RecursiveBuildTree(start_element.Get(), root_bounds.left, root_bounds.top,
                       &tree);
  } else {
    // If the search failed, start dumping with the first thing that isn't a
    // Pane.
    // TODO(http://crbug.com/1071188): Figure out why the original FindFirst
    // fails and remove this fallback codepath.
    Microsoft::WRL::ComPtr<IUIAutomationElement> non_pane_descendant;
    Microsoft::WRL::ComPtr<IUIAutomationCondition> is_pane_condition;
    base::win::ScopedVariant pane_control_type_variant(UIA_PaneControlTypeId);
    uia_->CreatePropertyCondition(UIA_ControlTypePropertyId,
                                  pane_control_type_variant,
                                  &is_pane_condition);
    Microsoft::WRL::ComPtr<IUIAutomationCondition> not_is_pane_condition;
    uia_->CreateNotCondition(is_pane_condition.Get(), &not_is_pane_condition);
    Microsoft::WRL::ComPtr<IUIAutomationElement> root;
    GetUIARoot(start, uia_.Get(), &root);
    CHECK(root.Get());
    root->FindFirst(TreeScope_Subtree, not_is_pane_condition.Get(),
                    &non_pane_descendant);

    DCHECK(non_pane_descendant.Get());
    RecursiveBuildTree(non_pane_descendant.Get(), root_bounds.left,
                       root_bounds.top, &tree);
  }
  return tree;
}

base::Value::Dict AXTreeFormatterUia::BuildTreeForSelector(
    const AXTreeSelector& selector) const {
  HWND hwnd = GetHWNDBySelector(selector);

  base::Value::Dict tree;
  if (hwnd) {
    Microsoft::WRL::ComPtr<IUIAutomationElement> root;
    uia_->ElementFromHandle(hwnd, &root);
    CHECK(root.Get());

    RECT root_bounds = {0};
    root->get_CurrentBoundingRectangle(&root_bounds);

    RecursiveBuildTree(root.Get(), root_bounds.left, root_bounds.top, &tree);
  }

  return tree;
}

base::Value::Dict AXTreeFormatterUia::BuildNode(
    AXPlatformNodeDelegate* node) const {
  Microsoft::WRL::ComPtr<IUIAutomationElement> uia_element;
  GetUIAElementFromDelegate(node, uia_.Get(), &uia_element);
  // Note that we have to go through external UIA APIs to get a reference to
  // the given node's UIA Element. This requires that the node is marked as
  // a content/control element (see IsUIAControl for more details). If you see
  // the following CHECK hit, most likely the node is not a UIA control and thus
  // not exposed via the Find* APIs.
  CHECK(uia_element.Get());

  RECT root_bounds = GetUIARootBounds(node, uia_.Get());
  base::Value::Dict tree;
  AddProperties(uia_element.Get(), root_bounds.left, root_bounds.top, &tree);
  return tree;
}

void AXTreeFormatterUia::RecursiveBuildTree(IUIAutomationElement* uncached_node,
                                            int root_x,
                                            int root_y,
                                            base::Value::Dict* dict) const {
  // Process this node.
  AddProperties(uncached_node, root_x, root_y, dict);

  // Update the cache to get children
  Microsoft::WRL::ComPtr<IUIAutomationElement> parent;
  uncached_node->BuildUpdatedCache(children_cache_request_.Get(), &parent);

  Microsoft::WRL::ComPtr<IUIAutomationElementArray> children;
  if (!SUCCEEDED(parent->GetCachedChildren(&children)) || !children)
    return;
  // Process children.
  base::Value::List child_list;
  int child_count;
  children->get_Length(&child_count);
  for (int i = 0; i < child_count; i++) {
    Microsoft::WRL::ComPtr<IUIAutomationElement> child;
    base::Value::Dict child_dict;
    if (SUCCEEDED(children->GetElement(i, &child))) {
      RecursiveBuildTree(child.Get(), root_x, root_y, &child_dict);
    } else {
      child_dict.Set("error", "[Error retrieving child]");
    }
    child_list.Append(std::move(child_dict));
  }
  dict->Set(kChildrenDictAttr, std::move(child_list));
}

void AXTreeFormatterUia::AddProperties(IUIAutomationElement* uncached_node,
                                       int root_x,
                                       int root_y,
                                       base::Value::Dict* dict) const {
  // Update the cache for this node's information.
  Microsoft::WRL::ComPtr<IUIAutomationElement> node;
  uncached_node->BuildUpdatedCache(element_cache_request_.Get(), &node);

  // Get all properties that may be on this node.
  for (long i : properties_) {
    base::win::ScopedVariant variant;
    if (SUCCEEDED(node->GetCachedPropertyValue(i, variant.Receive()))) {
      WriteProperty(i, variant, dict, root_x, root_y);
    }
  }
  // Add control pattern specific properties
  AddAnnotationProperties(node.Get(), dict);
  AddExpandCollapseProperties(node.Get(), dict);
  AddGridProperties(node.Get(), dict);
  AddGridItemProperties(node.Get(), dict);
  AddRangeValueProperties(node.Get(), dict);
  AddScrollProperties(node.Get(), dict);
  AddSelectionProperties(node.Get(), dict);
  AddSelectionItemProperties(node.Get(), dict);
  AddTableProperties(node.Get(), dict);
  AddToggleProperties(node.Get(), dict);
  AddValueProperties(node.Get(), dict);
  AddValueProperties(node.Get(), dict);
  AddWindowProperties(node.Get(), dict);
  AddCustomProperties(node.Get(), dict);
}

void AXTreeFormatterUia::AddAnnotationProperties(
    IUIAutomationElement* node,
    base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationAnnotationPattern> annotation_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_AnnotationPatternId,
                                         IID_PPV_ARGS(&annotation_pattern))) &&
      annotation_pattern) {
    int type_id;
    if (SUCCEEDED(annotation_pattern->get_CachedAnnotationTypeId(&type_id))) {
      const char* type_id_string;
      switch (type_id) {
        case AnnotationType_Comment:
          type_id_string = "Comment";
          break;
        case AnnotationType_Endnote:
          type_id_string = "Endnote";
          break;
        case AnnotationType_Footnote:
          type_id_string = "Footnote";
          break;
        case AnnotationType_Highlighted:
          type_id_string = "Highlighted";
          break;
        case AnnotationType_Unknown:
          type_id_string = "Unknown";
          break;
      }
      dict->SetByDottedPath("Annotation.AnnotationTypeId", type_id_string);
    }

    base::win::ScopedBstr type_name;
    if (SUCCEEDED(annotation_pattern->get_CachedAnnotationTypeName(
            type_name.Receive()))) {
      dict->SetByDottedPath("Annotation.AnnotationTypeName",
                            BstrToUTF8(type_name.Get()));
    }
  }
}

void AXTreeFormatterUia::AddExpandCollapseProperties(
    IUIAutomationElement* node,
    base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationExpandCollapsePattern>
      expand_collapse_pattern;
  if (SUCCEEDED(
          node->GetCachedPatternAs(UIA_ExpandCollapsePatternId,
                                   IID_PPV_ARGS(&expand_collapse_pattern))) &&
      expand_collapse_pattern) {
    ExpandCollapseState current_state;
    if (SUCCEEDED(expand_collapse_pattern->get_CachedExpandCollapseState(
            &current_state))) {
      std::string state;
      switch (current_state) {
        case ExpandCollapseState_Collapsed:
          state = "Collapsed";
          break;
        case ExpandCollapseState_Expanded:
          state = "Expanded";
          break;
        case ExpandCollapseState_PartiallyExpanded:
          state = "PartiallyExpanded";
          break;
        case ExpandCollapseState_LeafNode:
          state = "LeafNode";
          break;
      }
      dict->SetByDottedPath("ExpandCollapse.ExpandCollapseState", state);
    }
  }
}

void AXTreeFormatterUia::AddGridProperties(IUIAutomationElement* node,
                                           base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationGridPattern> grid_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_GridPatternId,
                                         IID_PPV_ARGS(&grid_pattern))) &&
      grid_pattern) {
    int column_count;
    if (SUCCEEDED(grid_pattern->get_CachedColumnCount(&column_count))) {
      dict->SetByDottedPath("Grid.ColumnCount", column_count);
    }
    int row_count;
    if (SUCCEEDED(grid_pattern->get_CachedRowCount(&row_count))) {
      dict->SetByDottedPath("Grid.RowCount", row_count);
    }
  }
}

void AXTreeFormatterUia::AddGridItemProperties(IUIAutomationElement* node,
                                               base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationGridItemPattern> grid_item_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_GridItemPatternId,
                                         IID_PPV_ARGS(&grid_item_pattern))) &&
      grid_item_pattern) {
    int column;
    if (SUCCEEDED(grid_item_pattern->get_CachedColumn(&column))) {
      dict->SetByDottedPath("GridItem.Column", column);
    }
    int column_span;
    if (SUCCEEDED(grid_item_pattern->get_CachedColumnSpan(&column_span))) {
      dict->SetByDottedPath("GridItem.ColumnSpan", column_span);
    }
    int row;
    if (SUCCEEDED(grid_item_pattern->get_CachedRow(&row))) {
      dict->SetByDottedPath("GridItem.Row", row);
    }
    int row_span;
    if (SUCCEEDED(grid_item_pattern->get_CachedRowSpan(&row_span))) {
      dict->SetByDottedPath("GridItem.RowSpan", row_span);
    }
    Microsoft::WRL::ComPtr<IUIAutomationElement> containing_grid;
    if (SUCCEEDED(
            grid_item_pattern->get_CachedContainingGrid(&containing_grid))) {
      dict->SetByDottedPath("GridItem.ContainingGrid",
                            GetNodeName(containing_grid.Get()));
    }
  }
}

void AXTreeFormatterUia::AddRangeValueProperties(
    IUIAutomationElement* node,
    base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationRangeValuePattern> range_value_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_RangeValuePatternId,
                                         IID_PPV_ARGS(&range_value_pattern))) &&
      range_value_pattern) {
    BOOL is_read_only;
    if (SUCCEEDED(range_value_pattern->get_CachedIsReadOnly(&is_read_only))) {
      dict->SetByDottedPath("RangeValue.IsReadOnly", !!is_read_only);
    }
    double large_change;
    if (SUCCEEDED(range_value_pattern->get_CachedLargeChange(&large_change))) {
      dict->SetByDottedPath("RangeValue.LargeChange", large_change);
    }
    double small_change;
    if (SUCCEEDED(range_value_pattern->get_CachedSmallChange(&small_change))) {
      dict->SetByDottedPath("RangeValue.SmallChange", small_change);
    }
    double maximum;
    if (SUCCEEDED(range_value_pattern->get_CachedMaximum(&maximum))) {
      dict->SetByDottedPath("RangeValue.Maximum", maximum);
    }
    double minimum;
    if (SUCCEEDED(range_value_pattern->get_CachedMinimum(&minimum))) {
      dict->SetByDottedPath("RangeValue.Minimum", minimum);
    }
    double value;
    if (SUCCEEDED(range_value_pattern->get_CachedValue(&value))) {
      dict->SetByDottedPath("RangeValue.Value", value);
    }
  }
}

void AXTreeFormatterUia::AddScrollProperties(IUIAutomationElement* node,
                                             base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationScrollPattern> scroll_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_ScrollPatternId,
                                         IID_PPV_ARGS(&scroll_pattern))) &&
      scroll_pattern) {
    double horizontal_scroll_percent;
    if (SUCCEEDED(scroll_pattern->get_CachedHorizontalScrollPercent(
            &horizontal_scroll_percent))) {
      dict->SetByDottedPath("Scroll.HorizontalScrollPercent",
                            horizontal_scroll_percent);
    }

    double horizontal_view_size;
    if (SUCCEEDED(scroll_pattern->get_CachedHorizontalViewSize(
            &horizontal_view_size))) {
      dict->SetByDottedPath("Scroll.HorizontalViewSize", horizontal_view_size);
    }
    BOOL horizontally_scrollable;
    if (SUCCEEDED(scroll_pattern->get_CachedHorizontallyScrollable(
            &horizontally_scrollable))) {
      dict->SetByDottedPath("Scroll.HorizontallyScrollable",
                            !!horizontally_scrollable);
    }

    double vertical_scroll_percent;
    if (SUCCEEDED(scroll_pattern->get_CachedVerticalScrollPercent(
            &vertical_scroll_percent))) {
      dict->SetByDottedPath("Scroll.VerticalScrollPercent",
                            vertical_scroll_percent);
    }

    double vertical_view_size;
    if (SUCCEEDED(
            scroll_pattern->get_CachedVerticalViewSize(&vertical_view_size))) {
      dict->SetByDottedPath("Scroll.VerticalViewSize", vertical_view_size);
    }
    BOOL vertically_scrollable;
    if (SUCCEEDED(scroll_pattern->get_CachedVerticallyScrollable(
            &vertically_scrollable))) {
      dict->SetByDottedPath("Scroll.VerticallyScrollable",
                            !!vertically_scrollable);
    }
  }
}

void AXTreeFormatterUia::AddSelectionProperties(IUIAutomationElement* node,
                                                base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationSelectionPattern> selection_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_SelectionPatternId,
                                         IID_PPV_ARGS(&selection_pattern))) &&
      selection_pattern) {
    BOOL can_select_multiple;
    if (SUCCEEDED(selection_pattern->get_CachedCanSelectMultiple(
            &can_select_multiple))) {
      dict->SetByDottedPath("Selection.CanSelectMultiple",
                            !!can_select_multiple);
    }
    BOOL is_selection_required;
    if (SUCCEEDED(selection_pattern->get_CachedIsSelectionRequired(
            &is_selection_required))) {
      dict->SetByDottedPath("Selection.IsSelectionRequired",
                            !!is_selection_required);
    }
  }
}

void AXTreeFormatterUia::AddSelectionItemProperties(
    IUIAutomationElement* node,
    base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationSelectionItemPattern>
      selection_item_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(
          UIA_SelectionItemPatternId, IID_PPV_ARGS(&selection_item_pattern))) &&
      selection_item_pattern) {
    BOOL is_selected;
    if (SUCCEEDED(selection_item_pattern->get_CachedIsSelected(&is_selected))) {
      dict->SetByDottedPath("SelectionItem.IsSelected", !!is_selected);
    }
    Microsoft::WRL::ComPtr<IUIAutomationElement> selection_container;
    if (SUCCEEDED(selection_item_pattern->get_CachedSelectionContainer(
            &selection_container))) {
      dict->SetByDottedPath("SelectionItem.SelectionContainer",
                            GetNodeName(selection_container.Get()));
    }
  }
}

void AXTreeFormatterUia::AddTableProperties(IUIAutomationElement* node,
                                            base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationTablePattern> table_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_TablePatternId,
                                         IID_PPV_ARGS(&table_pattern))) &&
      table_pattern) {
    RowOrColumnMajor row_or_column_major;
    if (SUCCEEDED(
            table_pattern->get_CachedRowOrColumnMajor(&row_or_column_major))) {
      std::string row_or_column_string;
      switch (row_or_column_major) {
        case RowOrColumnMajor_RowMajor:
          row_or_column_string = "RowMajor";
          break;
        case RowOrColumnMajor_ColumnMajor:
          row_or_column_string = "ColumnMajor";
          break;
        case RowOrColumnMajor_Indeterminate:
          row_or_column_string = "Indeterminate";
          break;
      }
      dict->SetByDottedPath("Table.RowOrColumnMajor", row_or_column_string);
    }
  }
}

void AXTreeFormatterUia::AddToggleProperties(IUIAutomationElement* node,
                                             base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationTogglePattern> toggle_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_TogglePatternId,
                                         IID_PPV_ARGS(&toggle_pattern))) &&
      toggle_pattern) {
    ToggleState toggle_state;
    if (SUCCEEDED(toggle_pattern->get_CachedToggleState(&toggle_state))) {
      std::string toggle_state_string;
      switch (toggle_state) {
        case ToggleState_Off:
          toggle_state_string = "Off";
          break;
        case ToggleState_On:
          toggle_state_string = "On";
          break;
        case ToggleState_Indeterminate:
          toggle_state_string = "Indeterminate";
          break;
      }
      dict->SetByDottedPath("Toggle.ToggleState", toggle_state_string);
    }
  }
}

void AXTreeFormatterUia::AddValueProperties(IUIAutomationElement* node,
                                            base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationValuePattern> value_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_ValuePatternId,
                                         IID_PPV_ARGS(&value_pattern))) &&
      value_pattern) {
    BOOL is_read_only;
    if (SUCCEEDED(value_pattern->get_CachedIsReadOnly(&is_read_only))) {
      dict->SetByDottedPath("Value.IsReadOnly", !!is_read_only);
    }
    base::win::ScopedBstr value;
    if (SUCCEEDED(value_pattern->get_CachedValue(value.Receive()))) {
      dict->SetByDottedPath("Value.Value", BstrToUTF8(value.Get()));
    }
  }
}

void AXTreeFormatterUia::AddWindowProperties(IUIAutomationElement* node,
                                             base::Value::Dict* dict) const {
  Microsoft::WRL::ComPtr<IUIAutomationWindowPattern> window_pattern;
  if (SUCCEEDED(node->GetCachedPatternAs(UIA_WindowPatternId,
                                         IID_PPV_ARGS(&window_pattern))) &&
      window_pattern) {
    BOOL is_modal;
    if (SUCCEEDED(window_pattern->get_CachedIsModal(&is_modal))) {
      dict->SetByDottedPath("Window.IsModal", !!is_modal);
    }
  }
}

std::map<long, std::string>& AXTreeFormatterUia::GetCustomPropertiesMap()
    const {
  static base::NoDestructor<std::map<long, std::string>> custom_properties_map;
  return *custom_properties_map;
}

void AXTreeFormatterUia::AddCustomProperties(IUIAutomationElement* node,
                                             base::Value::Dict* dict) const {
  // Custom properties need to be added separately.
  for (const auto& property : GetCustomPropertiesMap()) {
    base::win::ScopedVariant variant;
    if (SUCCEEDED(
            node->GetCurrentPropertyValue(property.first, variant.Receive()))) {
      WriteProperty(property.first, variant, dict);
    }
  }
}

std::string AXTreeFormatterUia::GetPropertyName(long property_id) const {
  // We cannot infer the property name from a custom property id, so we get it
  // from the map we created manually in `BuildCustomPropertiesMap()`.
  auto property = GetCustomPropertiesMap().find(property_id);
  if (property != GetCustomPropertiesMap().end()) {
    return property->second;
  }
  return UiaIdentifierToCondensedString(property_id);
}

void AXTreeFormatterUia::WriteProperty(long propertyId,
                                       const base::win::ScopedVariant& var,
                                       base::Value::Dict* dict,
                                       int root_x,
                                       int root_y) const {
  switch (var.type()) {
    case VT_EMPTY:
    case VT_NULL:
      break;
    case VT_I2:
      dict->SetByDottedPath(GetPropertyName(propertyId), var.ptr()->iVal);
      break;
    case VT_I4:
      WriteI4Property(propertyId, var.ptr()->lVal, dict);
      break;
    case VT_R4:
      dict->SetByDottedPath(GetPropertyName(propertyId), var.ptr()->fltVal);
      break;
    case VT_R8:
      dict->SetByDottedPath(GetPropertyName(propertyId), var.ptr()->dblVal);
      break;
    case VT_I1:
      dict->SetByDottedPath(GetPropertyName(propertyId), var.ptr()->cVal);
      break;
    case VT_UI1:
      dict->SetByDottedPath(GetPropertyName(propertyId), var.ptr()->bVal);
      break;
    case VT_UI2:
      dict->SetByDottedPath(GetPropertyName(propertyId), var.ptr()->uiVal);
      break;
    case VT_UI4:
      dict->SetByDottedPath(GetPropertyName(propertyId),
                            static_cast<int>(var.ptr()->ulVal));
      break;
    case VT_BSTR:
      dict->SetByDottedPath(GetPropertyName(propertyId),
                            BstrToUTF8(var.ptr()->bstrVal));
      break;
    case VT_BOOL:
      dict->SetByDottedPath(GetPropertyName(propertyId),
                            var.ptr()->boolVal == VARIANT_TRUE ? true : false);
      break;
    case VT_UNKNOWN:
      WriteUnknownProperty(propertyId, var.ptr()->punkVal, dict);
      break;
    default:
      switch (propertyId) {
        case UIA_BoundingRectanglePropertyId:
          WriteRectangleProperty(propertyId, var, root_x, root_y, dict);
          break;
        default:
          break;
      }
      break;
  }
}

void AXTreeFormatterUia::WriteI4Property(long propertyId,
                                         long lval,
                                         base::Value::Dict* dict) const {
  switch (propertyId) {
    case UIA_ControlTypePropertyId:
      dict->SetByDottedPath(GetPropertyName(propertyId),
                            UiaIdentifierToCondensedString(lval));
      break;
    case UIA_OrientationPropertyId:
      dict->SetByDottedPath(GetPropertyName(propertyId),
                            base::WideToUTF8(UiaOrientationToString(lval)));
      break;
    case UIA_LiveSettingPropertyId:
      dict->SetByDottedPath(GetPropertyName(propertyId),
                            base::WideToUTF8(UiaLiveSettingToString(lval)));
      break;
    default:
      dict->SetByDottedPath(GetPropertyName(propertyId),
                            static_cast<int>(lval));
      break;
  }
}

void AXTreeFormatterUia::WriteUnknownProperty(long propertyId,
                                              IUnknown* unk,
                                              base::Value::Dict* dict) const {
  switch (propertyId) {
    case UIA_ControllerForPropertyId:
    case UIA_DescribedByPropertyId:
    case UIA_FlowsFromPropertyId:
    case UIA_FlowsToPropertyId: {
      Microsoft::WRL::ComPtr<IUIAutomationElementArray> array;
      if (unk && SUCCEEDED(unk->QueryInterface(IID_PPV_ARGS(&array))))
        WriteElementArray(propertyId, array.Get(), dict);
      break;
    }
    case UIA_LabeledByPropertyId: {
      Microsoft::WRL::ComPtr<IUIAutomationElement> node;
      if (unk && SUCCEEDED(unk->QueryInterface(IID_PPV_ARGS(&node)))) {
        dict->SetByDottedPath(GetPropertyName(propertyId),
                              GetNodeName(node.Get()));
      }
      break;
    }
    default:
      break;
  }
}

void AXTreeFormatterUia::WriteRectangleProperty(long propertyId,
                                                const VARIANT& value,
                                                int root_x,
                                                int root_y,
                                                base::Value::Dict* dict) const {
  CHECK(value.vt == (VT_ARRAY | VT_R8));

  double* data = nullptr;
  SafeArrayAccessData(value.parray, reinterpret_cast<void**>(&data));

  base::Value::Dict rectangle;
  rectangle.Set("left", static_cast<int>(data[0] - root_x));
  rectangle.Set("top", static_cast<int>(data[1] - root_y));
  rectangle.Set("width", static_cast<int>(data[2]));
  rectangle.Set("height", static_cast<int>(data[3]));
  dict->SetByDottedPath(GetPropertyName(propertyId), std::move(rectangle));

  SafeArrayUnaccessData(value.parray);
}

void AXTreeFormatterUia::WriteElementArray(long propertyId,
                                           IUIAutomationElementArray* array,
                                           base::Value::Dict* dict) const {
  int count;
  array->get_Length(&count);
  std::u16string element_list;
  for (int i = 0; i < count; i++) {
    Microsoft::WRL::ComPtr<IUIAutomationElement> element;
    if (SUCCEEDED(array->GetElement(i, &element))) {
      if (element_list != u"") {
        element_list += u", ";
      }
      auto name = GetNodeName(element.Get());
      if (name.empty()) {
        base::win::ScopedBstr role;
        element->get_CurrentAriaRole(role.Receive());
        name = u"{" + base::WideToUTF16(role.Get()) + u"}";
      }
      element_list += name;
    }
  }
  if (!element_list.empty()) {
    dict->SetByDottedPath(GetPropertyName(propertyId), element_list);
  }
}

std::u16string AXTreeFormatterUia::GetNodeName(
    IUIAutomationElement* uncached_node) const {
  // Update the cache for this node.
  if (uncached_node) {
    Microsoft::WRL::ComPtr<IUIAutomationElement> node;
    uncached_node->BuildUpdatedCache(element_cache_request_.Get(), &node);

    base::win::ScopedBstr name;
    base::win::ScopedVariant variant;
    if (SUCCEEDED(node->GetCachedPropertyValue(UIA_NamePropertyId,
                                               variant.Receive())) &&
        variant.type() == VT_BSTR) {
      return base::WideToUTF16(
          {variant.ptr()->bstrVal, SysStringLen(variant.ptr()->bstrVal)});
    }
  }
  return std::u16string();
}

void AXTreeFormatterUia::BuildCacheRequests() {
  // Create cache request for requesting children of a node.
  uia_->CreateCacheRequest(&children_cache_request_);
  CHECK(children_cache_request_.Get());
  children_cache_request_->put_TreeScope(TreeScope_Children);

  // Set filter to include all nodes in the raw view.
  Microsoft::WRL::ComPtr<IUIAutomationCondition> raw_view_condition;
  uia_->get_RawViewCondition(&raw_view_condition);
  CHECK(raw_view_condition.Get());
  children_cache_request_->put_TreeFilter(raw_view_condition.Get());

  // Create cache request for requesting information about a node.
  uia_->CreateCacheRequest(&element_cache_request_);
  CHECK(element_cache_request_.Get());
  element_cache_request_->put_TreeScope(TreeScope_Element);

  // Caching properties allows us to use GetCachedPropertyValue.
  // The non-cached version (GetCurrentPropertyValue) may cross
  // the process boundary for each call.

  // Cache all properties.
  for (long i : properties_) {
    element_cache_request_->AddProperty(i);
  }
  // Cache all patterns.
  for (long i : patterns_) {
    element_cache_request_->AddPattern(i);
  }
  // Cache pattern properties
  for (long i : pattern_properties_) {
    element_cache_request_->AddProperty(i);
  }
}

void AXTreeFormatterUia::BuildCustomPropertiesMap() {
  GetCustomPropertiesMap().insert(
      {UiaRegistrarWin::GetInstance().GetVirtualContentPropertyId(),
       "VirtualContent"});
}

std::string AXTreeFormatterUia::ProcessTreeForOutput(
    const base::Value::Dict& dict) const {
  std::string line;

  // Always show control type, and show it first.
  const std::string* control_type_value =
      dict.FindStringByDottedPath(GetPropertyName(UIA_ControlTypePropertyId));
  if (control_type_value) {
    WriteAttribute(true, *control_type_value, &line);
  }

  // Properties.
  for (long i : properties_) {
    ProcessPropertyForOutput(GetPropertyName(i), dict, line);
  }

  // Custom properties.
  for (const auto& i : GetCustomPropertiesMap()) {
    ProcessPropertyForOutput(GetPropertyName(i.first), dict, line);
  }

  // Patterns.
  const std::string pattern_property_names[] = {
      // UIA_AnnotationPatternId
      "Annotation.AnnotationTypeId", "Annotation.AnnotationTypeName",
      // UIA_ExpandCollapsePatternId
      "ExpandCollapse.ExpandCollapseState",
      // UIA_GridPatternId
      "Grid.ColumnCount", "Grid.RowCount",
      // UIA_GridItemPatternId
      "GridItem.Column", "GridItem.ColumnSpan", "GridItem.Row",
      "GridItem.RowSpan", "GridItem.ContainingGrid",
      // UIA_RangeValuePatternId
      "RangeValue.IsReadOnly", "RangeValue.LargeChange",
      "RangeValue.SmallChange", "RangeValue.Maximum", "RangeValue.Minimum",
      "RangeValue.Value",
      // UIA_ScrollPatternId
      "Scroll.HorizontalScrollPercent", "Scroll.HorizontalViewSize",
      "Scroll.HorizontallyScrollable", "Scroll.VerticalScrollPercent",
      "Scroll.VerticalViewSize", "Scroll.VerticallyScrollable",
      // UIA_SelectionPatternId
      "Selection.CanSelectMultiple", "Selection.IsSelectionRequired",
      // UIA_SelectionItemPatternId
      "SelectionItem.IsSelected", "SelectionItem.SelectionContainer",
      // UIA_TablePatternId
      "Table.RowOrColumnMajor",
      // UIA_TogglePatternId
      "Toggle.ToggleState",
      // UIA_ValuePatternId
      "Value.IsReadOnly", "Value.Value",
      // UIA_WindowPatternId
      "Window.IsModal"};

  for (const std::string& pattern_property_name : pattern_property_names) {
    ProcessPropertyForOutput(pattern_property_name, dict, line);
  }

  return line;
}

void AXTreeFormatterUia::ProcessPropertyForOutput(
    const std::string& property_name,
    const base::Value::Dict& dict,
    std::string& line) const {
  const base::Value* value = dict.FindByDottedPath(property_name);
  if (value) {
    ProcessValueForOutput(property_name, *value, line);
  }
}

void AXTreeFormatterUia::ProcessValueForOutput(const std::string& name,
                                               const base::Value& value,
                                               std::string& line) const {
  switch (value.type()) {
    case base::Value::Type::STRING: {
      WriteAttribute(false,
                     base::StringPrintf("%s='%s'", name.c_str(),
                                        value.GetString().c_str()),
                     &line);
      break;
    }
    case base::Value::Type::BOOLEAN: {
      WriteAttribute(false,
                     base::StringPrintf("%s=%s", name.c_str(),
                                        (value.GetBool() ? "true" : "false")),
                     &line);
      break;
    }
    case base::Value::Type::INTEGER: {
      WriteAttribute(false,
                     base::StringPrintf("%s=%d", name.c_str(), value.GetInt()),
                     &line);
      break;
    }
    case base::Value::Type::DOUBLE: {
      WriteAttribute(
          false, base::StringPrintf("%s=%.2f", name.c_str(), value.GetDouble()),
          &line);
      break;
    }
    case base::Value::Type::DICT: {
      if (name == "BoundingRectangle") {
        WriteAttribute(false,
                       FormatRectangle(value.GetDict(), "BoundingRectangle",
                                       "left", "top", "width", "height"),
                       &line);
      }
      break;
    }
    default:
      NOTREACHED_IN_MIGRATION();
      break;
  }
}

}  // namespace ui