blob: 981d34f792e4ede1fa141a3290ed52797b179e91 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include <vector>
#include "base/test/metrics/histogram_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_view_transition_callback.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/pseudo_element.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/testing/mock_function_scope.h"
#include "third_party/blink/renderer/core/view_transition/dom_view_transition.h"
#include "third_party/blink/renderer/core/view_transition/view_transition.h"
#include "third_party/blink/renderer/core/view_transition/view_transition_supplement.h"
#include "third_party/blink/renderer/core/view_transition/view_transition_utils.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
#include "third_party/blink/renderer/modules/accessibility/testing/accessibility_test.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
// TODO(nektar): Break test up into multiple tests.
TEST_F(AccessibilityTest, IsARIAWidget) {
String test_content =
"<body>"
"<span id=\"plain\">plain</span><br>"
"<span id=\"button\" role=\"button\">button</span><br>"
"<span id=\"button-parent\" "
"role=\"button\"><span>button-parent</span></span><br>"
"<span id=\"button-caps\" role=\"BUTTON\">button-caps</span><br>"
"<span id=\"button-second\" role=\"another-role "
"button\">button-second</span><br>"
"<span id=\"aria-bogus\" aria-bogus=\"bogus\">aria-bogus</span><br>"
"<span id=\"aria-selected\" aria-selected>aria-selected</span><br>"
"<span id=\"haspopup\" "
"aria-haspopup=\"true\">aria-haspopup-true</span><br>"
"<div id=\"focusable\" tabindex=\"1\">focusable</div><br>"
"<div tabindex=\"2\"><div "
"id=\"focusable-parent\">focusable-parent</div></div><br>"
"</body>";
SetBodyInnerHTML(test_content);
Element* root(GetDocument().documentElement());
EXPECT_FALSE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("plain"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("button"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("button-parent"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("button-caps"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("button-second"))));
EXPECT_FALSE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("aria-bogus"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("aria-selected"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("haspopup"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("focusable"))));
EXPECT_TRUE(AXObjectCache::IsInsideFocusableElementOrARIAWidget(
*root->getElementById(AtomicString("focusable-parent"))));
}
TEST_F(AccessibilityTest, HistogramTest) {
SetBodyInnerHTML("<body><button>Press Me</button></body>");
auto& cache = GetAXObjectCache();
cache.SetAXMode(ui::kAXModeBasic);
// No logs initially.
base::HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Snapshot", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.Float", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.Int", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.HTML", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.String", 0);
{
ui::AXTreeUpdate response;
ScopedFreezeAXCache freeze(cache);
cache.SerializeEntireTree(/* max_node_count */ 1000,
base::TimeDelta::FiniteMax(), &response);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Snapshot", 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.Float", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.Int", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.HTML", 0);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.String", 0);
}
{
std::vector<ui::AXTreeUpdate> updates;
std::vector<ui::AXEvent> events;
bool had_end_of_test_event = true;
bool had_load_complete_messages = true;
bool need_to_send_location_changes = false;
ScopedFreezeAXCache freeze(cache);
cache.SerializeDirtyObjectsAndEvents(updates, events, had_end_of_test_event,
had_load_complete_messages,
need_to_send_location_changes);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Snapshot", 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental", 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.Float", 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.Int", 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.HTML", 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.AXObjectCacheImpl.Incremental.String", 1);
}
}
TEST_F(AccessibilityTest, RemoveReferencesToAXID) {
auto& cache = GetAXObjectCache();
SetBodyInnerHTML(R"HTML(
<div id="f" style="position:fixed">aaa</div>
<h2 id="h">Heading</h2>)HTML");
AXObject* fixed = GetAXObjectByElementId("f");
// GetBoundsInFrameCoordinates() updates fixed_or_sticky_node_ids_.
fixed->GetBoundsInFrameCoordinates();
EXPECT_EQ(1u, cache.fixed_or_sticky_node_ids_.size());
// RemoveReferencesToAXID() on node that is not fixed or sticky should not
// affect fixed_or_sticky_node_ids_.
cache.RemoveReferencesToAXID(GetAXObjectByElementId("h")->AXObjectID());
EXPECT_EQ(1u, cache.fixed_or_sticky_node_ids_.size());
// RemoveReferencesToAXID() on node that fixed should affect
// fixed_or_sticky_node_ids_.
cache.RemoveReferencesToAXID(GetAXObjectByElementId("f")->AXObjectID());
EXPECT_EQ(0u, cache.fixed_or_sticky_node_ids_.size());
}
class MockAXObject : public AXObject {
public:
explicit MockAXObject(AXObjectCacheImpl& ax_object_cache)
: AXObject(ax_object_cache) {}
static unsigned num_children_changed_calls_;
void ChildrenChangedWithCleanLayout() final { num_children_changed_calls_++; }
Document* GetDocument() const final { return &AXObjectCache().GetDocument(); }
void AddChildren() final {}
ax::mojom::blink::Role NativeRoleIgnoringAria() const override {
return ax::mojom::blink::Role::kUnknown;
}
};
unsigned MockAXObject::num_children_changed_calls_ = 0;
TEST_F(AccessibilityTest, PauseUpdatesAfterMaxNumberQueued) {
auto& document = GetDocument();
auto* ax_object_cache =
To<AXObjectCacheImpl>(document.ExistingAXObjectCache());
DCHECK(ax_object_cache);
wtf_size_t max_updates = 10;
ax_object_cache->SetMaxPendingUpdatesForTesting(max_updates);
MockAXObject* ax_obj = MakeGarbageCollected<MockAXObject>(*ax_object_cache);
ax_object_cache->AssociateAXID(ax_obj);
for (unsigned i = 0; i < max_updates + 1; i++) {
ax_object_cache->DeferTreeUpdate(
AXObjectCacheImpl::TreeUpdateReason::kChildrenChanged, ax_obj);
}
ax_object_cache->ProcessCleanLayoutCallbacks(document);
ASSERT_EQ(0u, MockAXObject::num_children_changed_calls_);
}
TEST_F(AccessibilityTest, UpdateAXForAllDocumentsAfterPausedUpdates) {
auto& document = GetDocument();
auto* ax_object_cache =
To<AXObjectCacheImpl>(document.ExistingAXObjectCache());
DCHECK(ax_object_cache);
wtf_size_t max_updates = 1;
ax_object_cache->SetMaxPendingUpdatesForTesting(max_updates);
UpdateAllLifecyclePhasesForTest();
AXObject* root = ax_object_cache->Root();
// Queue one update too many.
ax_object_cache->DeferTreeUpdate(
AXObjectCacheImpl::TreeUpdateReason::kChildrenChanged, root);
ax_object_cache->DeferTreeUpdate(
AXObjectCacheImpl::TreeUpdateReason::kChildrenChanged, root);
ax_object_cache->UpdateAXForAllDocuments();
ScopedFreezeAXCache freeze(*ax_object_cache);
CHECK(!root->NeedsToUpdateCachedValues());
}
class AXViewTransitionTest : public testing::Test {
public:
AXViewTransitionTest() {}
void SetUp() override {
web_view_helper_ = std::make_unique<frame_test_helpers::WebViewHelper>();
web_view_helper_->Initialize();
web_view_helper_->Resize(gfx::Size(200, 200));
}
void TearDown() override { web_view_helper_.reset(); }
Document& GetDocument() {
return *web_view_helper_->GetWebView()
->MainFrameImpl()
->GetFrame()
->GetDocument();
}
void UpdateAllLifecyclePhasesAndFinishDirectives() {
UpdateAllLifecyclePhasesForTest();
for (auto& callback :
LayerTreeHost()->TakeViewTransitionCallbacksForTesting()) {
std::move(callback).Run();
}
}
cc::LayerTreeHost* LayerTreeHost() {
return web_view_helper_->LocalMainFrame()
->FrameWidgetImpl()
->LayerTreeHostForTesting();
}
void SetHtmlInnerHTML(const String& content) {
GetDocument().body()->setInnerHTML(content);
UpdateAllLifecyclePhasesForTest();
}
void UpdateAllLifecyclePhasesForTest() {
web_view_helper_->GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
}
using State = ViewTransition::State;
State GetState(DOMViewTransition* transition) const {
return transition->GetViewTransitionForTest()->state_;
}
protected:
test::TaskEnvironment task_environment_;
std::unique_ptr<frame_test_helpers::WebViewHelper> web_view_helper_;
};
TEST_F(AXViewTransitionTest, TransitionPseudoNotRelevant) {
SetHtmlInnerHTML(R"HTML(
<style>
.shared {
width: 100px;
height: 100px;
view-transition-name: shared;
contain: layout;
background: green;
}
</style>
<div id=target class=shared></div>
)HTML");
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
ExceptionState& exception_state = v8_scope.GetExceptionState();
MockFunctionScope funcs(script_state);
auto* view_transition_callback =
V8ViewTransitionCallback::Create(funcs.ExpectCall());
auto* transition = ViewTransitionSupplement::startViewTransition(
script_state, GetDocument(), view_transition_callback, exception_state);
ScriptPromiseTester finish_tester(script_state,
transition->finished(script_state));
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(GetState(transition), State::kCapturing);
UpdateAllLifecyclePhasesAndFinishDirectives();
EXPECT_EQ(GetState(transition), State::kDOMCallbackRunning);
// We should have a start request from the async callback passed to start()
// resolving.
test::RunPendingTasks();
UpdateAllLifecyclePhasesAndFinishDirectives();
// We should have a transition pseudo
auto* transition_pseudo = GetDocument().documentElement()->GetPseudoElement(
kPseudoIdViewTransition);
ASSERT_TRUE(transition_pseudo);
auto* container_pseudo = transition_pseudo->GetPseudoElement(
kPseudoIdViewTransitionGroup, AtomicString("shared"));
ASSERT_TRUE(container_pseudo);
auto* image_wrapper_pseudo = container_pseudo->GetPseudoElement(
kPseudoIdViewTransitionImagePair, AtomicString("shared"));
ASSERT_TRUE(image_wrapper_pseudo);
auto* incoming_image_pseudo = image_wrapper_pseudo->GetPseudoElement(
kPseudoIdViewTransitionNew, AtomicString("shared"));
ASSERT_TRUE(incoming_image_pseudo);
auto* outgoing_image_pseudo = image_wrapper_pseudo->GetPseudoElement(
kPseudoIdViewTransitionOld, AtomicString("shared"));
ASSERT_TRUE(outgoing_image_pseudo);
ASSERT_TRUE(transition_pseudo->GetLayoutObject());
ASSERT_TRUE(container_pseudo->GetLayoutObject());
ASSERT_TRUE(image_wrapper_pseudo->GetLayoutObject());
ASSERT_TRUE(incoming_image_pseudo->GetLayoutObject());
ASSERT_TRUE(outgoing_image_pseudo->GetLayoutObject());
EXPECT_FALSE(AXObjectCacheImpl::IsRelevantPseudoElement(*transition_pseudo));
EXPECT_FALSE(AXObjectCacheImpl::IsRelevantPseudoElement(*container_pseudo));
EXPECT_FALSE(
AXObjectCacheImpl::IsRelevantPseudoElement(*image_wrapper_pseudo));
EXPECT_FALSE(
AXObjectCacheImpl::IsRelevantPseudoElement(*incoming_image_pseudo));
EXPECT_FALSE(
AXObjectCacheImpl::IsRelevantPseudoElement(*outgoing_image_pseudo));
}
} // namespace blink