Add per-browser ax_viewport_collapse setting

Adds role-aware collapsing of off-screen nodes to CDP accessibility tree
serialization (Accessibility.getFullAXTree). When enabled, off-screen
landmarks and headings are serialized as summaries (role + name only)
and other off-screen nodes are pruned. Position:fixed/sticky descendants
of off-screen parents are still discovered and fully serialized.

Reduces snapshot size for AI agents using Playwright ariaSnapshot().
Platform screen readers are unaffected — they use a separate code path.
CDP nodesUpdated events are suppressed when active.

The setting is a per-browser CefBrowserSettings field threaded through
WebPreferences, with a runtime override via SetAxViewportCollapse().
Both APIs are guarded with CEF_API_ADDED(CEF_EXPERIMENTAL).
This commit is contained in:
Marshall Greenblatt committed 2026-03-27 19:05:51 -04:00
1 parent 2d766e51dd
commit b6e302c3aa
23 files changed
+2129 -13

No files matched your search

+1
View File
@@ -550,6 +550,7 @@
'ceftests_sources_common': [
'tests/ceftests/api_version_unittest.cc',
'tests/ceftests/audio_output_unittest.cc',
'tests/ceftests/ax_viewport_collapse_unittest.cc',
'tests/ceftests/base/atomic_flag_unittest.cc',
'tests/ceftests/base/bind_unittest.cc',
'tests/ceftests/base/callback_helpers_unittest.cc',
+17
View File
@@ -1072,6 +1072,23 @@ class CefBrowserHost : public virtual CefBaseRefCounted {
///
/*--cef(default_retval=CEF_RUNTIME_STYLE_DEFAULT)--*/
virtual cef_runtime_style_t GetRuntimeStyle() = 0;
#if CEF_API_ADDED(CEF_EXPERIMENTAL)
///
/// Enable or disable CDP accessibility tree viewport collapse for this
/// browser. When enabled, off-screen landmarks and headings are serialized
/// as summaries and other off-screen nodes are pruned. Overrides the
/// CefBrowserSettings.ax_viewport_collapse value. If called on the UI thread
/// the change will be applied immediately. Otherwise, the change will be
/// applied asynchronously on the UI thread.
/// WARNING: This collapses the CDP accessibility tree and disables CDP
/// dynamic tree updates (nodesUpdated events). The DevTools Accessibility
/// panel will show an incomplete tree. Platform screen readers (NVDA, JAWS,
/// VoiceOver) are unaffected — they use a separate code path.
///
/*--cef(added=experimental)--*/
virtual void SetAxViewportCollapse(bool enabled) = 0;
#endif
};
#endif // CEF_INCLUDE_CEF_BROWSER_H_
+16
View File
@@ -721,6 +721,22 @@ typedef struct _cef_browser_settings_t {
/// supported with Chrome style.
///
cef_state_t chrome_zoom_bubble;
#if CEF_API_ADDED(CEF_EXPERIMENTAL)
///
/// Controls whether CDP accessibility tree serialization collapses off-screen
/// nodes. When enabled, off-screen landmarks and headings are serialized as
/// summaries (role + name only) and other off-screen nodes are pruned.
/// This reduces snapshot size for AI agents using Playwright ariaSnapshot().
/// WARNING: This collapses the CDP accessibility tree and disables CDP
/// dynamic tree updates (nodesUpdated events). The DevTools Accessibility
/// panel will show an incomplete tree. Platform screen readers (NVDA, JAWS,
/// VoiceOver) are unaffected — they use a separate code path.
/// Can also be configured at runtime using
/// CefBrowserHost::SetAxViewportCollapse.
///
cef_state_t ax_viewport_collapse;
#endif
} cef_browser_settings_t;
///
+3
View File
@@ -580,6 +580,9 @@ struct CefBrowserSettingsTraits {
target->chrome_status_bubble = src->chrome_status_bubble;
target->chrome_zoom_bubble = src->chrome_zoom_bubble;
#if CEF_API_ADDED(CEF_EXPERIMENTAL)
target->ax_viewport_collapse = src->ax_viewport_collapse;
#endif
}
};
+14
View File
@@ -976,6 +976,20 @@ cef_runtime_style_t CefBrowserHostBase::GetRuntimeStyle() {
return IsAlloyStyle() ? CEF_RUNTIME_STYLE_ALLOY : CEF_RUNTIME_STYLE_CHROME;
}
void CefBrowserHostBase::SetAxViewportCollapse(bool enabled) {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&CefBrowserHostBase::SetAxViewportCollapse,
this, enabled));
return;
}
ax_viewport_collapse_ = enabled;
if (auto* web_contents = GetWebContents()) {
web_contents->OnWebPreferencesChanged();
}
}
void CefBrowserHostBase::ReplaceMisspelling(const CefString& word) {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(
+7
View File
@@ -279,6 +279,7 @@ class CefBrowserHostBase : public CefBrowserHost,
void ExitFullscreen(bool will_cause_resize) override;
bool IsRenderProcessUnresponsive() override;
cef_runtime_style_t GetRuntimeStyle() override;
void SetAxViewportCollapse(bool enabled) override;
// CefBrowser methods:
bool IsValid() override;
@@ -385,6 +386,9 @@ class CefBrowserHostBase : public CefBrowserHost,
}
bool is_views_hosted() const { return is_views_hosted_; }
SkColor GetBackgroundColor() const;
std::optional<bool> ax_viewport_collapse() const {
return ax_viewport_collapse_;
}
// Returns true if windowless rendering is enabled.
virtual bool IsWindowless() const = 0;
@@ -518,6 +522,9 @@ class CefBrowserHostBase : public CefBrowserHost,
};
std::optional<AutoResizeConstraints> auto_resize_;
// Runtime override for CDP accessibility tree viewport collapse.
std::optional<bool> ax_viewport_collapse_;
private:
IMPLEMENT_REFCOUNTING(CefBrowserHostBase);
};
@@ -426,6 +426,12 @@ void ChromeContentBrowserClientCef::OverrideWebPreferences(
if (browser) {
renderer_prefs::SetCefPrefs(browser->settings(), *prefs);
// Runtime override takes precedence over CefBrowserSettings.
if (browser->ax_viewport_collapse().has_value()) {
prefs->accessibility_viewport_collapse =
browser->ax_viewport_collapse().value();
}
// Set the background color for the WebView.
base_background_color = browser->GetBackgroundColor();
} else {
+5
View File
@@ -5,6 +5,7 @@
#include "cef/libcef/browser/prefs/renderer_prefs.h"
#include "base/command_line.h"
#include "cef/libcef/common/api_version_util.h"
#include "cef/libcef/common/cef_switches.h"
#include "content/public/common/content_switches.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
@@ -94,6 +95,10 @@ void SetCefPrefs(const CefBrowserSettings& cef,
SET_STATE(cef.tab_to_links, web.tabs_to_links);
SET_STATE(cef.local_storage, web.local_storage_enabled);
if (CEF_API_IS_ADDED(CEF_EXPERIMENTAL)) {
SET_STATE(cef.ax_viewport_collapse, web.accessibility_viewport_collapse);
}
// Never explicitly enable GPU-related functions in this method because the
// GPU blacklist is not being checked here.
if (cef.webgl == STATE_DISABLED) {
@@ -0,0 +1,61 @@
// Copyright (c) 2026 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
// Included from inspector_type_builder_helper.cc via patch.
// Builds a minimal AXNode for off-screen landmarks and headings:
// role, name, and heading level (if applicable). No children.
#include "ui/accessibility/ax_role_properties.h"
namespace blink {
std::unique_ptr<AXNode> BuildCollapsedSummaryNode(AXObject& ax_object) {
auto node = AXNode::create()
.setNodeId(String::Number(ax_object.AXObjectID()))
.setIgnored(false)
.build();
ax::mojom::blink::Role role = ax_object.RoleValue();
node->setRole(CreateRoleNameValue(role));
node->setChromeRole(CreateInternalRoleValue(role));
// Accessible name.
ax::mojom::blink::NameFrom name_from;
String computed_name = ax_object.GetName(name_from, nullptr, nullptr);
node->setName(CreateValue(computed_name, AXValueTypeEnum::ComputedString));
// Heading level.
if (ui::IsHeading(static_cast<ax::mojom::Role>(role))) {
auto properties = std::make_unique<protocol::Array<AXProperty>>();
int level = ax_object.HeadingLevel();
if (level > 0) {
properties->emplace_back(
CreateProperty(AXPropertyNameEnum::Level, CreateValue(level)));
}
node->setProperties(std::move(properties));
}
// Empty children.
node->setChildIds(std::make_unique<protocol::Array<AXNodeId>>());
// DOM node reference.
Node* dom_node = ax_object.GetNode();
if (dom_node) {
node->setBackendDOMNodeId(IdentifiersFactory::IntIdForNode(dom_node));
}
// Parent reference.
const AXObject* parent = ax_object.ParentObjectIncludedInTree();
if (parent) {
node->setParentId(String::Number(parent->AXObjectID()));
} else if (ax_object.GetDocument() && ax_object.GetDocument()->GetFrame()) {
auto& frame_token =
ax_object.GetDocument()->GetFrame()->GetDevToolsFrameToken();
node->setFrameId(IdentifiersFactory::IdFromToken(frame_token));
}
return node;
}
} // namespace blink
@@ -0,0 +1,62 @@
// Copyright (c) 2026 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include "cef/libcef/renderer/accessibility/viewport_collapse.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object.h"
#include "ui/accessibility/ax_role_properties.h"
namespace cef {
bool IsViewportCollapseEnabled(blink::Document& document) {
auto* settings = document.GetSettings();
return settings && settings->GetAccessibilityViewportCollapse();
}
blink::PhysicalRect ComputeViewportRect(blink::Document& document) {
blink::LocalFrameView* frame_view = document.View();
// The visual viewport's VisibleContentRect accounts for pinch-zoom scroll
// but not layout viewport scroll. For a normal (non-zoomed) page, the
// visual viewport offset is (0,0) and all scrolling is on the layout
// viewport. We need the full visible area in document coordinates, so
// combine both: start from the visual viewport rect (which has the correct
// size and visual viewport offset) and add the layout viewport scroll.
auto rect = blink::PhysicalRect(
frame_view->GetPage()->GetVisualViewport().VisibleContentRect());
if (auto* layout_viewport = frame_view->LayoutViewport()) {
blink::ScrollOffset scroll = layout_viewport->GetScrollOffset();
rect.offset.left += blink::LayoutUnit(scroll.x());
rect.offset.top += blink::LayoutUnit(scroll.y());
}
return rect;
}
bool IsNodeInViewport(const blink::AXObject& ax_object,
const blink::PhysicalRect& viewport_rect) {
blink::PhysicalRect bounds = ax_object.GetBoundsInFrameCoordinates();
return bounds.Intersects(viewport_rect);
}
ViewportCollapseAction ClassifyOffScreenNode(ax::mojom::blink::Role role) {
ax::mojom::Role r = static_cast<ax::mojom::Role>(role);
if (ui::IsLandmark(r) || ui::IsHeading(r)) {
return ViewportCollapseAction::kSummary;
}
// Chromium maps <footer> to kFooter and <header> to kHeader, which are
// distinct from kContentInfo/kBanner used by IsLandmark(). Treat them
// as landmarks for collapsing purposes.
if (r == ax::mojom::Role::kFooter || r == ax::mojom::Role::kHeader) {
return ViewportCollapseAction::kSummary;
}
return ViewportCollapseAction::kPrune;
}
} // namespace cef
@@ -0,0 +1,36 @@
// Copyright (c) 2026 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#ifndef CEF_LIBCEF_RENDERER_ACCESSIBILITY_VIEWPORT_COLLAPSE_H_
#define CEF_LIBCEF_RENDERER_ACCESSIBILITY_VIEWPORT_COLLAPSE_H_
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "ui/accessibility/ax_enums.mojom-blink.h"
namespace blink {
class AXObject;
class Document;
} // namespace blink
namespace cef {
enum class ViewportCollapseAction { kSummary, kPrune };
// Check if viewport collapse is enabled for this document (reads Blink
// Settings).
bool IsViewportCollapseEnabled(blink::Document& document);
// Compute viewport rect from the document's visual viewport.
blink::PhysicalRect ComputeViewportRect(blink::Document& document);
// Check if a node intersects the viewport.
bool IsNodeInViewport(const blink::AXObject& ax_object,
const blink::PhysicalRect& viewport_rect);
// Classify an off-screen node by role.
ViewportCollapseAction ClassifyOffScreenNode(ax::mojom::blink::Role role);
} // namespace cef
#endif // CEF_LIBCEF_RENDERER_ACCESSIBILITY_VIEWPORT_COLLAPSE_H_
@@ -0,0 +1,170 @@
// Copyright 2026 The Chromium Embedded Framework Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Included from inspector_accessibility_agent.cc via patch (anonymous
// namespace). Viewport-collapse-aware variants of AddChildren and
// WalkAXNodesToDepth.
// Check if a node might be in-viewport despite having an off-screen parent.
// Only position:fixed and position:sticky elements can escape their parent's
// off-screen bounds. This is much cheaper than GetBoundsInFrameCoordinates().
bool MayBeVisibleDespiteOffscreenParent(const AXObject& ax_object) {
auto* layout = ax_object.GetLayoutObject();
return layout &&
(layout->IsFixedPositioned() || layout->IsStickyPositioned());
}
struct BfsEntry {
AXID id;
int depth;
bool parent_offscreen;
};
void AddChildrenWithCollapse(AXObject& ax_object,
bool follow_ignored,
std::unique_ptr<protocol::Array<AXNode>>& nodes,
AXObjectCacheImpl& cache,
const PhysicalRect& viewport_rect,
HashSet<AXID>& serialized_ids,
HashSet<AXID>& in_viewport_ids,
bool parent_offscreen,
Deque<BfsEntry>* queue,
int depth,
int max_depth) {
// Track IDs of nodes reached via pruned off-screen parents. If such a
// node turns out to be in-viewport (e.g. position:fixed), it must be
// enqueued in the BFS since the normal child-enqueue loop won't find it.
HashSet<AXID> from_pruned_ids;
HeapVector<Member<AXObject>> reachable;
reachable.Append(ax_object.ChildrenIncludingIgnored().rbegin(),
ax_object.ChildrenIncludingIgnored().rend());
while (!reachable.empty()) {
AXObject* descendant = reachable.back();
reachable.pop_back();
if (descendant->IsDetached()) {
continue;
}
bool from_pruned = from_pruned_ids.Contains(descendant->AXObjectID());
if (follow_ignored &&
(descendant->IsIgnoredButIncludedInTree() || !descendant->GetNode())) {
const auto& children = descendant->ChildrenIncludingIgnored();
reachable.Append(children.rbegin(), children.rend());
if (from_pruned) {
for (const auto& child : children) {
from_pruned_ids.insert(child->AXObjectID());
}
}
}
// Determine viewport status. If parent is off-screen, skip the expensive
// bounds check unless this node has fixed/sticky positioning.
bool effectively_offscreen = parent_offscreen || from_pruned;
bool in_viewport;
if (effectively_offscreen &&
!MayBeVisibleDespiteOffscreenParent(*descendant)) {
in_viewport = false;
} else {
in_viewport = cef::IsNodeInViewport(*descendant, viewport_rect);
}
if (!in_viewport) {
auto action = cef::ClassifyOffScreenNode(descendant->RoleValue());
if (action == cef::ViewportCollapseAction::kSummary) {
// Landmark/heading: emit summary, cut subtree entirely.
nodes->emplace_back(BuildCollapsedSummaryNode(*descendant));
serialized_ids.insert(descendant->AXObjectID());
} else {
// Pruned generic node: still visit children so that position:fixed
// or position:sticky descendants can escape the off-screen parent.
const auto& children = descendant->ChildrenIncludingIgnored();
reachable.Append(children.rbegin(), children.rend());
for (const auto& child : children) {
from_pruned_ids.insert(child->AXObjectID());
}
}
continue;
}
in_viewport_ids.insert(descendant->AXObjectID());
auto child_node = BuildProtocolAXNodeForAXObject(*descendant);
serialized_ids.insert(descendant->AXObjectID());
nodes->emplace_back(std::move(child_node));
// If this in-viewport node was discovered via a pruned off-screen parent,
// it won't be enqueued by the normal BFS child loop (which only checks
// direct unignored children). Enqueue it here so its subtree is visited.
if (from_pruned && queue && (max_depth == -1 || depth < max_depth)) {
queue->emplace_back(BfsEntry{descendant->AXObjectID(), depth + 1, false});
}
}
}
std::unique_ptr<protocol::Array<AXNode>> WalkAXNodesToDepthWithCollapse(
AXObjectCacheImpl& cache,
int max_depth) {
auto nodes = std::make_unique<protocol::Array<AXNode>>();
HashSet<AXID> serialized_ids;
HashSet<AXID> in_viewport_ids; // Tracks which nodes passed bounds check
PhysicalRect viewport_rect = cef::ComputeViewportRect(cache.GetDocument());
Deque<BfsEntry> queue;
queue.emplace_back(BfsEntry{cache.Root()->AXObjectID(), 1, false});
nodes->emplace_back(BuildProtocolAXNodeForAXObject(*cache.Root()));
serialized_ids.insert(cache.Root()->AXObjectID());
in_viewport_ids.insert(cache.Root()->AXObjectID());
while (!queue.empty()) {
auto [id, depth, parent_offscreen] = queue.front();
queue.pop_front();
AXObject* ax_object = cache.ObjectFromAXID(id);
if (!ax_object) {
continue;
}
// Serialize children — records viewport status in in_viewport_ids.
// Passes queue so that in-viewport nodes found under pruned parents
// can be enqueued for further traversal.
AddChildrenWithCollapse(*ax_object, true, nodes, cache, viewport_rect,
serialized_ids, in_viewport_ids, parent_offscreen,
&queue, depth, max_depth);
// Enqueue in-viewport unignored children for depth traversal.
// Uses in_viewport_ids instead of recomputing bounds.
const auto& children = ax_object->UnignoredChildrenSlow();
for (auto& child : children) {
bool child_in_viewport = in_viewport_ids.Contains(child->AXObjectID());
if (!child_in_viewport) {
// Already handled (serialized as summary or pruned) in
// AddChildrenWithCollapse. Don't recurse.
continue;
}
if (max_depth == -1 || depth < max_depth) {
queue.emplace_back(BfsEntry{child->AXObjectID(), depth + 1, false});
}
}
}
// Filter childIds to only reference serialized nodes.
// Summary nodes already have empty childIds so this only matters for
// full nodes whose children were partially pruned.
for (auto& node : *nodes) {
auto* child_ids = node->getChildIds(nullptr);
if (!child_ids || child_ids->empty()) {
continue;
}
auto filtered = std::make_unique<protocol::Array<AXNodeId>>();
for (const auto& child_id : *child_ids) {
if (serialized_ids.Contains(StringToIntStrict(child_id).value_or(0))) {
filtered->emplace_back(child_id);
}
}
node->setChildIds(std::move(filtered));
}
return nodes;
}
+6
View File
@@ -850,5 +850,11 @@ patches = [
# Support CEF component updater API.
# https://github.com/chromiumembedded/cef/issues/4087
'name': 'component_updater_4087'
},
{
# Support per-browser accessibility viewport collapse for CDP tree collapsing.
# Collapses off-screen nodes in CDP accessibility serialization to reduce
# snapshot size for AI agents. Controlled via CefBrowserSettings.
'name': 'blink_ax_viewport_collapse',
}
]
@@ -0,0 +1,271 @@
diff --git third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
index 23672617af41f1d88b551da7b4ad0a19e39a2092..6e9b90bad48d090a7f52b3598d0e57c7cbb66713 100644
--- third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
+++ third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
@@ -232,6 +232,7 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
out->lazy_load_enabled = data.lazy_load_enabled();
out->allow_mixed_content_upgrades = data.allow_mixed_content_upgrades();
out->always_show_focus = data.always_show_focus();
+ out->accessibility_viewport_collapse = data.accessibility_viewport_collapse();
out->touch_drag_drop_enabled = data.touch_drag_drop_enabled();
out->touch_dragend_context_menu = data.touch_dragend_context_menu();
out->webxr_immersive_ar_allowed = data.webxr_immersive_ar_allowed();
diff --git third_party/blink/public/common/web_preferences/web_preferences.h third_party/blink/public/common/web_preferences/web_preferences.h
index cd34e8f534e86d4ff11ea1d8020d215f37cd730b..c2218516b5e09fae2629db13ca8f9f83d30f617a 100644
--- third_party/blink/public/common/web_preferences/web_preferences.h
+++ third_party/blink/public/common/web_preferences/web_preferences.h
@@ -423,6 +423,9 @@ struct BLINK_COMMON_EXPORT WebPreferences {
// forcing :focus-visible to match regardless of focus method).
bool always_show_focus = false;
+ // Whether CDP accessibility tree serialization collapses off-screen nodes.
+ bool accessibility_viewport_collapse = false;
+
// Whether touch input can trigger HTML drag-and-drop operations. The
// default value depends on the platform.
bool touch_drag_drop_enabled = false;
diff --git third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
index 9bb06fb430cbf31b6fc1343229191565a62421a8..0656126a6ae01ce603cc56247b79a40f53dccc07 100644
--- third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
+++ third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
@@ -785,6 +785,11 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView,
return r.always_show_focus;
}
+ static bool accessibility_viewport_collapse(
+ const blink::web_pref::WebPreferences& r) {
+ return r.accessibility_viewport_collapse;
+ }
+
static bool touch_drag_drop_enabled(
const blink::web_pref::WebPreferences& r) {
return r.touch_drag_drop_enabled;
diff --git third_party/blink/public/mojom/webpreferences/web_preferences.mojom third_party/blink/public/mojom/webpreferences/web_preferences.mojom
index ec6f13d3924cf861c505ed6d3abde0371fd65475..195d7a3a6c464c959239b55bd114f9c1a0b65131 100644
--- third_party/blink/public/mojom/webpreferences/web_preferences.mojom
+++ third_party/blink/public/mojom/webpreferences/web_preferences.mojom
@@ -482,6 +482,9 @@ struct WebPreferences {
// forcing :focus-visible to match regardless of focus method).
bool always_show_focus;
+ // Whether CDP accessibility tree serialization collapses off-screen nodes.
+ bool accessibility_viewport_collapse;
+
// Whether touch input can trigger HTML drag-and-drop operations. The
// default value depends on the platform.
bool touch_drag_drop_enabled;
diff --git third_party/blink/public/web/web_settings.h third_party/blink/public/web/web_settings.h
index 42dcdc73fd3255fc4af472ddcc885dd9444f9bae..d21caf62ce8f7175f9ffbd7432013b9b48680d57 100644
--- third_party/blink/public/web/web_settings.h
+++ third_party/blink/public/web/web_settings.h
@@ -228,6 +228,7 @@ class WebSettings {
virtual void SetAccessibilityFontScaleFactor(float) = 0;
virtual void SetAccessibilityTextSizeContrastFactor(int) = 0;
virtual void SetAccessibilityAlwaysShowFocus(bool) = 0;
+ virtual void SetAccessibilityViewportCollapse(bool) = 0;
virtual void SetTextTrackKindUserPreference(TextTrackKindUserPreference) = 0;
virtual void SetTextTrackBackgroundColor(const WebString&) = 0;
virtual void SetTextTrackFontFamily(const WebString&) = 0;
diff --git third_party/blink/renderer/core/exported/web_settings_impl.cc third_party/blink/renderer/core/exported/web_settings_impl.cc
index e3532e382addf2efde5e60d0f4ae985cac8232a0..3a6551e8aeaba43d4c91b94211b99e5e620dbf10 100644
--- third_party/blink/renderer/core/exported/web_settings_impl.cc
+++ third_party/blink/renderer/core/exported/web_settings_impl.cc
@@ -175,6 +175,10 @@ void WebSettingsImpl::SetAccessibilityAlwaysShowFocus(bool always_show_focus) {
settings_->SetAccessibilityAlwaysShowFocus(always_show_focus);
}
+void WebSettingsImpl::SetAccessibilityViewportCollapse(bool enabled) {
+ settings_->SetAccessibilityViewportCollapse(enabled);
+}
+
void WebSettingsImpl::SetAccessibilityPasswordValuesEnabled(bool enabled) {
settings_->SetAccessibilityPasswordValuesEnabled(enabled);
}
diff --git third_party/blink/renderer/core/exported/web_settings_impl.h third_party/blink/renderer/core/exported/web_settings_impl.h
index 333634ea2567af0114a951ae47535ec42435cc9e..28bf3e044d69c8be0fed3d63f778bc551ce3797b 100644
--- third_party/blink/renderer/core/exported/web_settings_impl.h
+++ third_party/blink/renderer/core/exported/web_settings_impl.h
@@ -174,6 +174,7 @@ class CORE_EXPORT WebSettingsImpl final : public WebSettings {
void SetAccessibilityFontScaleFactor(float) override;
void SetAccessibilityTextSizeContrastFactor(int) override;
void SetAccessibilityAlwaysShowFocus(bool) override;
+ void SetAccessibilityViewportCollapse(bool) override;
void SetTextTrackKindUserPreference(TextTrackKindUserPreference) override;
void SetTextTrackBackgroundColor(const WebString&) override;
void SetTextTrackFontFamily(const WebString&) override;
diff --git third_party/blink/renderer/core/exported/web_view_impl.cc third_party/blink/renderer/core/exported/web_view_impl.cc
index f5bdcb8176a94705a3710ba5dbc536a96b35fed9..742ac4bcc69f02779535c376e2897f706a671eec 100644
--- third_party/blink/renderer/core/exported/web_view_impl.cc
+++ third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1834,6 +1834,8 @@ void WebView::ApplyWebPreferences(const web_pref::WebPreferences& prefs,
settings->SetForceDarkModeEnabled(prefs.force_dark_mode_enabled);
settings->SetAccessibilityAlwaysShowFocus(prefs.always_show_focus);
+ settings->SetAccessibilityViewportCollapse(
+ prefs.accessibility_viewport_collapse);
settings->SetAutoplayPolicy(prefs.autoplay_policy);
settings->SetRequireTransientActivationForGetDisplayMedia(
prefs.require_transient_activation_for_get_display_media);
@@ -3250,6 +3252,7 @@ void WebViewImpl::OnWindowShowStateChanged(
WasRestored();
}
break;
+ case WindowShowState::kHidden:
case WindowShowState::kInactive:
case WindowShowState::kFullscreen:
case WindowShowState::kEnd:
diff --git third_party/blink/renderer/core/frame/settings.json5 third_party/blink/renderer/core/frame/settings.json5
index d9d3a09b53e250b7a019195214186add0b85d562..9de56e24eca711dc82700ad3f49ed92a32ee4c75 100644
--- third_party/blink/renderer/core/frame/settings.json5
+++ third_party/blink/renderer/core/frame/settings.json5
@@ -1231,6 +1231,10 @@
name: "accessibilityIncludeSvgGElement",
initial: false,
},
+ {
+ name: "accessibilityViewportCollapse",
+ initial: false,
+ },
{
name: "bypassCSP",
initial: false,
diff --git third_party/blink/renderer/modules/accessibility/BUILD.gn third_party/blink/renderer/modules/accessibility/BUILD.gn
index f9de92e534a5eaaa012633b72e593a3075bbf48c..50bd9bac40ab76c1eda9eb3bc964ca41a087918f 100644
--- third_party/blink/renderer/modules/accessibility/BUILD.gn
+++ third_party/blink/renderer/modules/accessibility/BUILD.gn
@@ -2,6 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//cef/libcef/features/features.gni")
import("//third_party/blink/renderer/modules/modules.gni")
blink_modules_sources("accessibility") {
@@ -74,6 +75,17 @@ blink_modules_sources("accessibility") {
]
}
+ if (enable_cef) {
+ sources += [
+ "//cef/libcef/renderer/accessibility/build_collapsed_summary_node.inc",
+ "//cef/libcef/renderer/accessibility/viewport_collapse.cc",
+ "//cef/libcef/renderer/accessibility/viewport_collapse.h",
+ "//cef/libcef/renderer/accessibility/walk_ax_nodes_with_collapse.inc",
+ ]
+ configs += [ "//cef/libcef/features:config" ]
+ deps += [ "//cef/libcef/features:deps" ]
+ }
+
if (is_android) {
defines = [ "REDUCE_AX_INLINE_TEXTBOXES" ]
}
diff --git third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc
index e7f9651c0865b36aecf96a44c1061a7a40aabf43..91f0feb8e2f31c53f9425b5d874b6dc71d0544c1 100644
--- third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc
+++ third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc
@@ -7,6 +7,7 @@
#include <memory>
#include "base/containers/adapters.h"
+#include "cef/libcef/features/features.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/dom/element.h"
@@ -30,6 +31,10 @@
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/ax_node_data.h"
+#if BUILDFLAG(ENABLE_CEF)
+#include "cef/libcef/renderer/accessibility/viewport_collapse.h"
+#endif
+
namespace blink {
namespace {
@@ -132,6 +137,10 @@ std::unique_ptr<protocol::Array<AXNode>> WalkAXNodesToDepth(
return nodes;
}
+#if BUILDFLAG(ENABLE_CEF)
+#include "cef/libcef/renderer/accessibility/walk_ax_nodes_with_collapse.inc"
+#endif
+
} // namespace
using EnabledAgentsMultimap =
@@ -245,7 +254,14 @@ protocol::Response InspectorAccessibilityAgent::getFullAXTree(
cache.UpdateAXForAllDocuments();
ScopedFreezeAXCache freeze(cache);
- *nodes = WalkAXNodesToDepth(cache, depth.value_or(-1));
+#if BUILDFLAG(ENABLE_CEF)
+ if (cef::IsViewportCollapseEnabled(cache.GetDocument())) {
+ *nodes = WalkAXNodesToDepthWithCollapse(cache, depth.value_or(-1));
+ } else
+#endif
+ {
+ *nodes = WalkAXNodesToDepth(cache, depth.value_or(-1));
+ }
return protocol::Response::Success();
}
@@ -509,6 +525,15 @@ void InspectorAccessibilityAgent::AXReadyCallback(Document& document) {
}
void InspectorAccessibilityAgent::ProcessPendingDirtyNodes(Document& document) {
+#if BUILDFLAG(ENABLE_CEF)
+ // Suppress nodesUpdated events when viewport collapse is active.
+ // These events send uncollapsed data, creating an inconsistent tree.
+ // AI agents call getFullAXTree() directly without enable(), so they
+ // never receive these events anyway.
+ if (cef::IsViewportCollapseEnabled(document))
+ return;
+#endif
+
auto now = base::Time::Now();
if (!last_sync_times_.Contains(&document))
diff --git third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.cc third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.cc
index ad0547c3c362ea975bca43b53592d88afbd15603..6dac1c010b2b3a694d1c0c87b335f2009fa30079 100644
--- third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.cc
+++ third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.cc
@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.h"
+#include "cef/libcef/features/features.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
@@ -922,3 +923,7 @@ std::unique_ptr<AXNode> BuildProtocolAXNodeForUnignoredAXObject(
}
} // namespace blink
+
+#if BUILDFLAG(ENABLE_CEF)
+#include "cef/libcef/renderer/accessibility/build_collapsed_summary_node.inc"
+#endif
diff --git third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.h third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.h
index 2c8d5a8aada7230bc141feed9e1af2e6e95d3664..ee1829e828b30eb33e9f4834db7ed3f81280862c 100644
--- third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.h
+++ third_party/blink/renderer/modules/accessibility/inspector_type_builder_helper.h
@@ -8,6 +8,8 @@
#include "third_party/blink/renderer/core/accessibility/axid.h"
#include "third_party/blink/renderer/core/inspector/protocol/accessibility.h"
+#include "cef/libcef/features/features.h"
+
namespace blink {
class AXObject;
@@ -25,6 +27,10 @@ std::unique_ptr<AXNode> BuildProtocolAXNodeForIgnoredAXObject(
bool force_name_and_role);
std::unique_ptr<AXNode> BuildProtocolAXNodeForUnignoredAXObject(AXObject&);
+#if BUILDFLAG(ENABLE_CEF)
+std::unique_ptr<AXNode> BuildCollapsedSummaryNode(AXObject& ax_object);
+#endif
+
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_INSPECTOR_TYPE_BUILDER_HELPER_H_
-12
View File
@@ -214,18 +214,6 @@ index c87f079a6541866fa00cc96e7dfed11d743ba182..17c949f873124021ae69b2fe9d469913
#if BUILDFLAG(IS_MAC)
// Set the view's active state (i.e., tint state of controls).
virtual void SetActive(bool active) = 0;
diff --git third_party/blink/renderer/core/exported/web_view_impl.cc third_party/blink/renderer/core/exported/web_view_impl.cc
index f5bdcb8176a94705a3710ba5dbc536a96b35fed9..09122a6fdc4dbdc61fb580110473265c6ee2c661 100644
--- third_party/blink/renderer/core/exported/web_view_impl.cc
+++ third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -3250,6 +3250,7 @@ void WebViewImpl::OnWindowShowStateChanged(
WasRestored();
}
break;
+ case WindowShowState::kHidden:
case WindowShowState::kInactive:
case WindowShowState::kFullscreen:
case WindowShowState::kEnd:
diff --git ui/aura/native_window_occlusion_tracker_win.cc ui/aura/native_window_occlusion_tracker_win.cc
index 883f23810672adf94c8b86f8ec54dc6ac9e7acbb..5d1a2b11c2cea3b247077f96c35780e40efa70f5 100644
--- ui/aura/native_window_occlusion_tracker_win.cc
@@ -241,6 +241,12 @@ void MainContextImpl::PopulateBrowserSettings(CefBrowserSettings* settings) {
settings->chrome_status_bubble = STATE_DISABLED;
settings->chrome_zoom_bubble = STATE_DISABLED;
}
#if CEF_API_ADDED(CEF_EXPERIMENTAL)
if (command_line_->HasSwitch(switches::kAxViewportCollapse)) {
settings->ax_viewport_collapse = STATE_ENABLED;
}
#endif
}
void MainContextImpl::PopulateOsrSettings(OsrRendererSettings* settings) {
File diff suppressed because it is too large. Load diff
+1
View File
@@ -65,5 +65,6 @@ const char kUseChromeStyleWindow[] = "use-chrome-style-window";
const char kShowOverlayBrowser[] = "show-overlay-browser";
const char kUseAngle[] = "use-angle";
const char kOzonePlatform[] = "ozone-platform";
const char kAxViewportCollapse[] = "ax-viewport-collapse";
} // namespace client::switches
+1
View File
@@ -59,6 +59,7 @@ extern const char kUseChromeStyleWindow[];
extern const char kShowOverlayBrowser[];
extern const char kUseAngle[];
extern const char kOzonePlatform[];
extern const char kAxViewportCollapse[];
} // namespace client::switches
@@ -0,0 +1,87 @@
# ax_viewport_collapse Debug Tools
Manual verification tools for the `ax_viewport_collapse` CefBrowserSettings feature.
These connect to a running cefclient via CDP to inspect the accessibility tree.
## Prerequisites
```
python3 -m pip install websocket-client
```
## Usage
### 1. Launch cefclient with remote debugging
```
cefclient.exe --remote-debugging-port=9222 --remote-allow-origins=* --ax-viewport-collapse
```
The `--ax-viewport-collapse` flag maps to `CefBrowserSettings.ax_viewport_collapse = STATE_ENABLED` in cefclient.
To load the test page directly:
```
cefclient.exe --remote-debugging-port=9222 --remote-allow-origins=* --ax-viewport-collapse --url=file:///<path>/test_viewport_collapse.html
```
### 2. Run the verification script
```
python3 verify_viewport_collapse.py
```
Or navigate to a URL first:
```
python3 verify_viewport_collapse.py --url https://example.com
```
### Options
| Flag | Description |
|------|-------------|
| `--host` | CDP host (default: `127.0.0.1`) |
| `--port` | CDP port (default: `9222`) |
| `--url` | Navigate to URL before fetching tree |
| `--raw` | Print full JSON response instead of summary |
### Comparing with/without collapse
Run two cefclient instances on different ports:
```
cefclient.exe --cache-path=<path1> --remote-debugging-port=9222 --remote-allow-origins=* --ax-viewport-collapse
cefclient.exe --cache-path=<path2> --remote-debugging-port=9223 --remote-allow-origins=*
```
```
python3 verify_viewport_collapse.py --port 9222 --raw > with.json
python3 verify_viewport_collapse.py --port 9223 --raw > without.json
diff with.json without.json
```
### Expected output (with collapse enabled)
```
AX Tree Summary
============================================================
Total nodes returned: 77
Ignored: 3
Full (with children):70
Summary (collapsed): 4
Collapsed summary nodes (off-screen landmarks/headings):
------------------------------------------------------------
region: "Content preferences"
region: "Audio quality"
region: "Display"
region: "Storage"
```
Off-screen landmark sections appear as leaf nodes with role + name only. In-viewport nodes are fully serialized with children.
## Files
- `verify_viewport_collapse.py` — CDP script that calls `Accessibility.getFullAXTree()` and prints a summary or raw JSON
- `test_viewport_collapse.html` — Test page with 6 landmark sections, 2 in viewport and 4 pushed off-screen by a spacer
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Viewport Collapse Test Page</title>
<style>
body { font-family: sans-serif; margin: 0; padding: 20px; }
section { margin: 20px 0; padding: 20px; border: 1px solid #ccc; border-radius: 8px; }
h2 { margin-top: 0; }
.spacer { height: 200vh; } /* Push later sections well off-screen */
</style>
</head>
<body>
<header role="banner">
<h1>Viewport Collapse Test</h1>
<nav role="navigation" aria-label="Main navigation">
<a href="#account">Account</a> |
<a href="#language">Language</a> |
<a href="#content">Content</a> |
<a href="#audio">Audio Quality</a> |
<a href="#display">Display</a> |
<a href="#storage">Storage</a>
</nav>
</header>
<main role="main">
<!-- These two sections should be IN the viewport -->
<section id="account" role="region" aria-label="Account">
<h2>Account</h2>
<p>Manage your account settings.</p>
<button>Edit login methods</button>
<button>Change password</button>
<label>Email: <input type="text" value="user@example.com"></label>
</section>
<section id="language" role="region" aria-label="Language">
<h2>Language</h2>
<label>Choose language:
<select>
<option selected>English</option>
<option>Spanish</option>
<option>French</option>
<option>German</option>
<option>Japanese</option>
</select>
</label>
</section>
<!-- Spacer to push remaining sections off-screen -->
<div class="spacer" aria-hidden="true"></div>
<!-- These sections should be OFF-screen and collapsed -->
<section id="content" role="region" aria-label="Content preferences">
<h2>Content preferences</h2>
<label><input type="checkbox" checked> Allow explicit content</label>
<p>When off, explicit music won't be played.</p>
</section>
<section id="audio" role="region" aria-label="Audio quality">
<h2>Audio quality</h2>
<label>Streaming quality:
<select>
<option>Automatic</option>
<option selected>High</option>
<option>Very high</option>
</select>
</label>
<label>Download quality:
<select>
<option selected>Normal</option>
<option>High</option>
</select>
</label>
<label><input type="checkbox"> Auto adjust quality</label>
</section>
<section id="display" role="region" aria-label="Display">
<h2>Display</h2>
<label><input type="checkbox"> Use compact library layout</label>
<label><input type="checkbox" checked> Show album art</label>
<button>Reset display settings</button>
</section>
<section id="storage" role="region" aria-label="Storage">
<h2>Storage</h2>
<p>Cache size: 2.4 GB</p>
<button>Clear cache</button>
<button>Manage downloads</button>
</section>
</main>
<footer role="contentinfo">
<p>&copy; 2026 Test App. All rights reserved.</p>
</footer>
</body>
</html>
@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""
Verify --ax-viewport-collapse behavior via raw CDP.
Usage:
1. Launch cefclient with:
out\\Debug_GN_x64\\cefclient.exe --remote-debugging-port=9222 --ax-viewport-collapse
2. Navigate to a page with scrollable content, e.g. the test page:
out\\Debug_GN_x64\\cefclient.exe --remote-debugging-port=9222 --ax-viewport-collapse \\
--url=file:///path/to/cef/tools/debug/ax_viewport_collapse/test_viewport_collapse.html
3. Run this script:
python cef/tools/debug/ax_viewport_collapse/verify_viewport_collapse.py [--port 9222]
The script connects via CDP, optionally navigates, calls Accessibility.getFullAXTree(),
and prints a summary showing which nodes were serialized as full, summary, or absent.
"""
import argparse
import json
import sys
import http.client
import websocket # pip install websocket-client
def get_ws_url(host, port):
"""Get the first available CDP target's websocket URL."""
conn = http.client.HTTPConnection(host, port)
conn.request("GET", "/json")
resp = conn.getresponse()
targets = json.loads(resp.read())
conn.close()
for t in targets:
if t.get("type") == "page" and "webSocketDebuggerUrl" in t:
return t["webSocketDebuggerUrl"]
raise RuntimeError(
f"No page target found on {host}:{port}. Targets: {json.dumps(targets, indent=2)}"
)
def cdp_send(ws, method, params=None, id_counter=[0]):
id_counter[0] += 1
msg = {"id": id_counter[0], "method": method, "params": params or {}}
ws.send(json.dumps(msg))
while True:
resp = json.loads(ws.recv())
if resp.get("id") == id_counter[0]:
if "error" in resp:
raise RuntimeError(f"CDP error: {resp['error']}")
return resp.get("result", {})
def navigate_and_wait(ws, url):
"""Navigate to URL and wait for load."""
cdp_send(ws, "Page.enable")
cdp_send(ws, "Page.navigate", {"url": url})
# Wait for loadEventFired
while True:
resp = json.loads(ws.recv())
if resp.get("method") == "Page.loadEventFired":
break
LANDMARK_ROLES = {
"banner", "complementary", "contentinfo", "form", "main", "navigation",
"region", "search"
}
HEADING_ROLES = {"heading"}
INTERACTIVE_ROLES = {
"button", "checkbox", "link", "textbox", "combobox", "listbox", "menuitem",
"radio", "slider", "switch", "tab", "searchbox", "spinbutton", "tree"
}
def classify_role(role_value):
"""Classify a CDP role string."""
r = (role_value or "").lower()
if r in LANDMARK_ROLES:
return "landmark"
if r in HEADING_ROLES:
return "heading"
if r in INTERACTIVE_ROLES:
return "interactive"
return "structural"
def analyze_tree(nodes):
"""Analyze the AX tree and print a summary."""
total = len(nodes)
ignored = 0
full_nodes = []
summary_nodes = [] # Has role+name but empty children
for node in nodes:
if node.get("ignored"):
ignored += 1
continue
child_ids = node.get("childIds", [])
role_obj = node.get("role", {})
role_value = role_obj.get("value", "") if isinstance(role_obj, dict) else ""
name_obj = node.get("name", {})
name_value = name_obj.get("value", "") if isinstance(name_obj, dict) else ""
properties = node.get("properties", [])
has_name = bool(name_value)
has_children = len(child_ids) > 0
has_full_props = len(properties) > 2 # More than just level
cat = classify_role(role_value)
if cat in ("landmark", "heading") and has_name and not has_children:
summary_nodes.append(node)
else:
full_nodes.append(node)
print(f"\n{'='*60}")
print(f"AX Tree Summary")
print(f"{'='*60}")
print(f"Total nodes returned: {total}")
print(f" Ignored: {ignored}")
print(f" Full (with children):{len(full_nodes)}")
print(f" Summary (collapsed): {len(summary_nodes)}")
print()
if summary_nodes:
print("Collapsed summary nodes (off-screen landmarks/headings):")
print("-" * 60)
for node in summary_nodes:
role_obj = node.get("role", {})
role_val = role_obj.get("value", "?") if isinstance(role_obj,
dict) else "?"
name_obj = node.get("name", {})
name_val = name_obj.get("value", "") if isinstance(name_obj, dict) else ""
props = node.get("properties", [])
level_str = ""
for p in props:
if isinstance(p, dict) and p.get("name") == "level":
lv = p.get("value", {})
level_str = f" [level={lv.get('value', '?')}]"
print(f" {role_val}: \"{name_val}\"{level_str}")
print()
# Print the tree structure (first 50 non-ignored nodes)
print("First 50 non-ignored nodes:")
print("-" * 60)
node_map = {n.get("nodeId"): n for n in nodes}
count = 0
for node in nodes:
if node.get("ignored"):
continue
if count >= 50:
print(" ... (truncated)")
break
count += 1
role_obj = node.get("role", {})
role_val = role_obj.get("value", "?") if isinstance(role_obj, dict) else "?"
name_obj = node.get("name", {})
name_val = name_obj.get("value", "") if isinstance(name_obj, dict) else ""
child_ids = node.get("childIds", [])
name_str = f" \"{name_val}\"" if name_val else ""
children_str = f" [{len(child_ids)} children]" if child_ids else " [leaf]"
is_summary = node in summary_nodes
tag = " <-- COLLAPSED" if is_summary else ""
print(f" {role_val}{name_str}{children_str}{tag}")
print()
def main():
parser = argparse.ArgumentParser(
description="Verify CDP accessibility viewport collapse")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=9222)
parser.add_argument("--url", help="URL to navigate to before fetching tree")
parser.add_argument(
"--raw", action="store_true", help="Print raw JSON response")
args = parser.parse_args()
print(f"Connecting to CDP at {args.host}:{args.port}...")
ws_url = get_ws_url(args.host, args.port)
print(f"WebSocket: {ws_url}")
ws = websocket.create_connection(ws_url)
if args.url:
print(f"Navigating to {args.url}...")
navigate_and_wait(ws, args.url)
print("Page loaded.")
print("Enabling accessibility domain...")
cdp_send(ws, "Accessibility.enable")
print("Fetching full AX tree...")
result = cdp_send(ws, "Accessibility.getFullAXTree")
nodes = result.get("nodes", [])
if args.raw:
print(json.dumps(nodes, indent=2))
else:
analyze_tree(nodes)
ws.close()
if __name__ == "__main__":
main()
+1 -1
View File
@@ -12,7 +12,7 @@ from git_util import get_changed_files
from yapf_util import yapf_format
# File extensions that can be formatted.
DEFAULT_LINT_WHITELIST_REGEX = r"(.*\.cpp|.*\.cc|.*\.c|.*\.h|.*\.java|.*\.mm|.*\.m|.*\.py)$"
DEFAULT_LINT_WHITELIST_REGEX = r"(.*\.cpp|.*\.cc|.*\.c|.*\.h|.*\.inc|.*\.java|.*\.mm|.*\.m|.*\.py)$"
DEFAULT_LINT_BLACKLIST_REGEX = r"$^"
# Directories containing these path components will be ignored.