summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordmazzoni <dmazzoni@chromium.org>2015-07-22 12:17:10 -0700
committerCommit bot <commit-bot@chromium.org>2015-07-22 19:18:36 +0000
commit1777fbdbd234ddc8e19d4ba3a42cfd3234b8a158 (patch)
tree76e29ea1fd30a3c40a577685c4bf7e19eb632ebe
parentbb2780cad4e11b3fdde2690428e90be003f5be73 (diff)
downloadchromium_src-1777fbdbd234ddc8e19d4ba3a42cfd3234b8a158.zip
chromium_src-1777fbdbd234ddc8e19d4ba3a42cfd3234b8a158.tar.gz
chromium_src-1777fbdbd234ddc8e19d4ba3a42cfd3234b8a158.tar.bz2
Re-land: Reimplement automation API on top of C++-backed AXTree.
Original review: https://codereview.chromium.org/1155183006 Landed in: r335183 Reverted in: r335343 (bug 502311) BUG=495323,502311 Review URL: https://codereview.chromium.org/1231603009 Cr-Commit-Position: refs/heads/master@{#339929}
-rw-r--r--chrome/browser/extensions/api/automation/automation_apitest.cc338
-rw-r--r--chrome/browser/extensions/api/automation_internal/automation_event_router.cc10
-rw-r--r--chrome/browser/extensions/api/automation_internal/automation_internal_api.cc67
-rw-r--r--chrome/browser/extensions/api/automation_internal/automation_util.cc215
-rw-r--r--chrome/browser/extensions/api/automation_internal/automation_util.h41
-rw-r--r--chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js2
-rw-r--r--chrome/browser/ui/aura/accessibility/automation_manager_aura.cc54
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/common/extensions/api/automation.idl196
-rw-r--r--chrome/common/extensions/api/automation_internal.idl44
-rw-r--r--chrome/renderer/extensions/automation_internal_custom_bindings.cc513
-rw-r--r--chrome/renderer/extensions/automation_internal_custom_bindings.h127
-rw-r--r--chrome/renderer/resources/extensions/automation/automation_node.js1090
-rw-r--r--chrome/renderer/resources/extensions/automation_custom_bindings.js90
-rw-r--r--chrome/test/data/extensions/api_test/automation/sites/attributes.html (renamed from chrome/test/data/extensions/api_test/automation/sites/mixins.html)29
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.html (renamed from chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.html)2
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js (renamed from chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.js)157
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js37
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/tree_change.js2
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/unit/manifest.json8
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/unit/test.js508
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/unit/unit.html6
-rw-r--r--content/browser/accessibility/browser_accessibility_manager_unittest.cc85
-rw-r--r--extensions/renderer/logging_native_handler.cc30
-rw-r--r--extensions/renderer/logging_native_handler.h3
-rw-r--r--extensions/renderer/script_context.cc30
-rw-r--r--extensions/renderer/script_context.h3
-rw-r--r--ui/accessibility/ax_tree.cc11
-rw-r--r--ui/accessibility/ax_tree_unittest.cc4
29 files changed, 1550 insertions, 2154 deletions
diff --git a/chrome/browser/extensions/api/automation/automation_apitest.cc b/chrome/browser/extensions/api/automation/automation_apitest.cc
index 8505161..5f1adcf 100644
--- a/chrome/browser/extensions/api/automation/automation_apitest.cc
+++ b/chrome/browser/extensions/api/automation/automation_apitest.cc
@@ -8,13 +8,15 @@
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/thread_task_runner_handle.h"
-#include "chrome/browser/extensions/api/automation_internal/automation_util.h"
+#include "chrome/browser/accessibility/ax_tree_id_registry.h"
+#include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
#include "chrome/browser/extensions/chrome_extension_function.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/automation_internal.h"
+#include "chrome/common/extensions/chrome_extension_messages.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/ax_event_notification_details.h"
#include "content/public/browser/render_widget_host.h"
@@ -99,11 +101,6 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) {
<< message_;
}
-IN_PROC_BROWSER_TEST_F(AutomationApiTest, Unit) {
- ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html"))
- << message_;
-}
-
IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) {
StartEmbeddedTestServer();
ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
@@ -203,10 +200,9 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) {
<< message_;
}
-// Flaky. http://crbug.com/467921
-IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_Mixins) {
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, Attributes) {
StartEmbeddedTestServer();
- ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "mixins.html"))
+ ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "attributes.html"))
<< message_;
}
@@ -216,328 +212,4 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, TreeChange) {
<< message_;
}
-
-static const int kPid = 1;
-static const int kTab0Rid = 1;
-static const int kTab1Rid = 2;
-
-using content::BrowserContext;
-
-typedef ui::AXTreeSerializer<const ui::AXNode*> TreeSerializer;
-typedef ui::AXTreeSource<const ui::AXNode*> TreeSource;
-
-#define AX_EVENT_ASSERT_EQUAL ui::AX_EVENT_LOAD_COMPLETE
-#define AX_EVENT_ASSERT_NOT_EQUAL ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
-#define AX_EVENT_IGNORE ui::AX_EVENT_CHILDREN_CHANGED
-#define AX_EVENT_TEST_COMPLETE ui::AX_EVENT_BLUR
-
-// This test is based on ui/accessibility/ax_generated_tree_unittest.cc
-// However, because the tree updates need to be sent to the extension, we can't
-// use a straightforward set of nested loops as that test does, so this class
-// keeps track of where we're up to in our imaginary loops, while the extension
-// function classes below do the work of actually incrementing the state when
-// appropriate.
-// The actual deserialization and comparison happens in the API bindings and the
-// test extension respectively: see
-// c/t/data/extensions/api_test/automation/tests/generated/generated_trees.js
-class TreeSerializationState {
- public:
- TreeSerializationState()
-#ifdef NDEBUG
- : tree_size(3),
-#else
- : tree_size(2),
-#endif
- generator(tree_size, true),
- num_trees(generator.UniqueTreeCount()),
- tree0_version(0),
- tree1_version(0) {
- }
-
- // Serializes tree and sends it as an accessibility event to the extension.
- void SendDataForTree(const ui::AXTree* tree,
- TreeSerializer* serializer,
- int routing_id,
- BrowserContext* browser_context) {
- ui::AXTreeUpdate update;
- serializer->SerializeChanges(tree->root(), &update);
- SendUpdate(update,
- ui::AX_EVENT_LAYOUT_COMPLETE,
- tree->root()->id(),
- routing_id,
- browser_context);
- }
-
- // Sends the given AXTreeUpdate to the extension as an accessibility event.
- void SendUpdate(ui::AXTreeUpdate update,
- ui::AXEvent event,
- int node_id,
- int routing_id,
- BrowserContext* browser_context) {
- content::AXEventNotificationDetails detail(update.node_id_to_clear,
- update.nodes,
- event,
- node_id,
- std::map<int32, int>(),
- kPid,
- routing_id);
- std::vector<content::AXEventNotificationDetails> details;
- details.push_back(detail);
- automation_util::DispatchAccessibilityEventsToAutomation(
- details, browser_context, gfx::Vector2d());
- }
-
- // Notify the extension bindings to destroy the tree for the given tab
- // (identified by routing_id)
- void SendTreeDestroyedEvent(int routing_id, BrowserContext* browser_context) {
- automation_util::DispatchTreeDestroyedEventToAutomation(
- kPid, routing_id, browser_context);
- }
-
- // Reset tree0 to a new generated tree based on tree0_version, reset
- // tree0_source accordingly.
- void ResetTree0() {
- tree0.reset(new ui::AXSerializableTree);
- tree0_source.reset(tree0->CreateTreeSource());
- generator.BuildUniqueTree(tree0_version, tree0.get());
- if (!serializer0.get())
- serializer0.reset(new TreeSerializer(tree0_source.get()));
- }
-
- // Reset tree0, set up serializer0, send down the initial tree data to create
- // the tree in the extension
- void InitializeTree0(BrowserContext* browser_context) {
- ResetTree0();
- serializer0->ChangeTreeSourceForTesting(tree0_source.get());
- serializer0->Reset();
- SendDataForTree(tree0.get(), serializer0.get(), kTab0Rid, browser_context);
- }
-
- // Reset tree1 to a new generated tree based on tree1_version, reset
- // tree1_source accordingly.
- void ResetTree1() {
- tree1.reset(new ui::AXSerializableTree);
- tree1_source.reset(tree1->CreateTreeSource());
- generator.BuildUniqueTree(tree1_version, tree1.get());
- if (!serializer1.get())
- serializer1.reset(new TreeSerializer(tree1_source.get()));
- }
-
- // Reset tree1, set up serializer1, send down the initial tree data to create
- // the tree in the extension
- void InitializeTree1(BrowserContext* browser_context) {
- ResetTree1();
- serializer1->ChangeTreeSourceForTesting(tree1_source.get());
- serializer1->Reset();
- SendDataForTree(tree1.get(), serializer1.get(), kTab1Rid, browser_context);
- }
-
- const int tree_size;
- const ui::TreeGenerator generator;
-
- // The loop variables: comments indicate which variables in
- // ax_generated_tree_unittest they correspond to.
- const int num_trees; // n
- int tree0_version; // i
- int tree1_version; // j
- int starting_node; // k
-
- // Tree infrastructure; tree0 and tree1 need to be regenerated whenever
- // tree0_version and tree1_version change, respectively; tree0_source and
- // tree1_source need to be reset whenever that happens.
- scoped_ptr<ui::AXSerializableTree> tree0, tree1;
- scoped_ptr<TreeSource> tree0_source, tree1_source;
- scoped_ptr<TreeSerializer> serializer0, serializer1;
-
- // Whether tree0 needs to be destroyed after the extension has performed its
- // checks
- bool destroy_tree0;
-};
-
-static TreeSerializationState state;
-
-// Override for chrome.automationInternal.enableTab
-// This fakes out the process and routing IDs for two "tabs", which contain the
-// source and target trees, respectively, and sends down the current tree for
-// the requested tab - tab 1 always has tree1, and tab 0 starts with tree0
-// and then has a series of updates intended to translate tree0 to tree1.
-// Once all the updates have been sent, the extension asserts that both trees
-// are equivalent, and then one or both of the trees are reset to a new version.
-class FakeAutomationInternalEnableTabFunction
- : public UIThreadExtensionFunction {
- public:
- FakeAutomationInternalEnableTabFunction() {}
-
- ExtensionFunction::ResponseAction Run() override {
- using api::automation_internal::EnableTab::Params;
- scoped_ptr<Params> params(Params::Create(*args_));
- EXTENSION_FUNCTION_VALIDATE(params.get());
- if (!params->args.tab_id.get())
- return RespondNow(Error("tab_id not specified"));
- int tab_id = *params->args.tab_id;
- if (tab_id == 0) {
- // tab 0 <--> tree0
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(&TreeSerializationState::InitializeTree0,
- base::Unretained(&state),
- base::Unretained(browser_context())));
- // TODO(aboxhall): Need to rewrite this test in terms of tree ids.
- return RespondNow(ArgumentList(
- api::automation_internal::EnableTab::Results::Create(0)));
- }
- if (tab_id == 1) {
- // tab 1 <--> tree1
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(&TreeSerializationState::InitializeTree1,
- base::Unretained(&state),
- base::Unretained(browser_context())));
- return RespondNow(ArgumentList(
- api::automation_internal::EnableTab::Results::Create(0)));
- }
- return RespondNow(Error("Unrecognised tab_id"));
- }
-};
-
-// Factory method for use in OverrideFunction()
-ExtensionFunction* FakeAutomationInternalEnableTabFunctionFactory() {
- return new FakeAutomationInternalEnableTabFunction();
-}
-
-// Helper method to serialize a series of updates via source_serializer to
-// transform the tree which source_serializer was initialized from into
-// target_tree, and then trigger the test code to assert the two tabs contain
-// the same tree.
-void TransformTree(TreeSerializer* source_serializer,
- ui::AXTree* target_tree,
- TreeSource* target_tree_source,
- content::BrowserContext* browser_context) {
- source_serializer->ChangeTreeSourceForTesting(target_tree_source);
- for (int node_delta = 0; node_delta < state.tree_size; ++node_delta) {
- int id = 1 + (state.starting_node + node_delta) % state.tree_size;
- ui::AXTreeUpdate update;
- source_serializer->SerializeChanges(target_tree->GetFromId(id), &update);
- bool is_last_update = node_delta == state.tree_size - 1;
- ui::AXEvent event =
- is_last_update ? AX_EVENT_ASSERT_EQUAL : AX_EVENT_IGNORE;
- state.SendUpdate(
- update, event, target_tree->root()->id(), kTab0Rid, browser_context);
- }
-}
-
-// Helper method to send a no-op tree update to tab 0 with the given event.
-void SendEvent(ui::AXEvent event, content::BrowserContext* browser_context) {
- ui::AXTreeUpdate update;
- ui::AXNode* root = state.tree0->root();
- state.serializer0->SerializeChanges(root, &update);
- state.SendUpdate(update, event, root->id(), kTab0Rid, browser_context);
-}
-
-// Override for chrome.automationInternal.performAction
-// This is used as a synchronization mechanism; the general flow is:
-// 1. The extension requests tree0 and tree1 (on tab 0 and tab 1 respectively)
-// 2. FakeAutomationInternalEnableTabFunction sends down the trees
-// 3. When the callback for getTree(0) fires, the extension calls doDefault() on
-// the root node of tree0, which calls into this class's Run() method.
-// 4. In the normal case, we're in the "inner loop" (iterating over
-// starting_node). For each value of starting_node, we do the following:
-// a. Serialize a sequence of updates which should transform tree0 into
-// tree1. Each of these updates is sent as a childrenChanged event,
-// except for the last which is sent as a loadComplete event.
-// b. state.destroy_tree0 is set to true
-// c. state.starting_node gets incremented
-// d. The loadComplete event triggers an assertion in the extension.
-// e. The extension performs another doDefault() on the root node of the
-// tree.
-// f. This time, we send a destroy event to tab0, so that the tree can be
-// reset.
-// g. The extension is notified of the tree's destruction and requests the
-// tree for tab 0 again, returning to step 2.
-// 5. When starting_node exceeds state.tree_size, we increment tree0_version if
-// it would not exceed state.num_trees, or increment tree1_version and reset
-// tree0_version to 0 otherwise, and reset starting_node to 0.
-// Then we reset one or both trees as appropriate, and send down destroyed
-// events similarly, causing the extension to re-request the tree and going
-// back to step 2 again.
-// 6. When tree1_version has gone through all possible values, we send a blur
-// event, signaling the extension to call chrome.test.succeed() and finish
-// the test.
-class FakeAutomationInternalPerformActionFunction
- : public UIThreadExtensionFunction {
- public:
- FakeAutomationInternalPerformActionFunction() {}
-
- ExtensionFunction::ResponseAction Run() override {
- if (state.destroy_tree0) {
- // Step 4.f: tell the extension to destroy the tree and re-request it.
- state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
- state.destroy_tree0 = false;
- return RespondNow(NoArguments());
- }
-
- TreeSerializer* serializer0 = state.serializer0.get();
- if (state.starting_node < state.tree_size) {
- // As a sanity check, if the trees are not equal, assert that they are not
- // equal before serializing changes.
- if (state.tree0_version != state.tree1_version)
- SendEvent(AX_EVENT_ASSERT_NOT_EQUAL, browser_context());
-
- // Step 4.a: pretend that tree0 turned into tree1, and serialize
- // a sequence of updates to tab 0 to match.
- TransformTree(serializer0,
- state.tree1.get(),
- state.tree1_source.get(),
- browser_context());
-
- // Step 4.b: remember that we need to tell the extension to destroy and
- // re-request the tree on the next action.
- state.destroy_tree0 = true;
-
- // Step 4.c: increment starting_node.
- state.starting_node++;
- } else if (state.tree0_version < state.num_trees - 1) {
- // Step 5: Increment tree0_version and reset starting_node
- state.tree0_version++;
- state.starting_node = 0;
-
- // Step 5: Reset tree0 and tell the extension to destroy and re-request it
- state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
- } else if (state.tree1_version < state.num_trees - 1) {
- // Step 5: Increment tree1_version and reset tree0_version and
- // starting_node
- state.tree1_version++;
- state.tree0_version = 0;
- state.starting_node = 0;
-
- // Step 5: Reset tree0 and tell the extension to destroy and re-request it
- state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
-
- // Step 5: Reset tree1 and tell the extension to destroy and re-request it
- state.SendTreeDestroyedEvent(kTab1Rid, browser_context());
- } else {
- // Step 6: Send a TEST_COMPLETE (blur) event to signal the extension to
- // call chrome.test.succeed().
- SendEvent(AX_EVENT_TEST_COMPLETE, browser_context());
- }
-
- return RespondNow(NoArguments());
- }
-};
-
-// Factory method for use in OverrideFunction()
-ExtensionFunction* FakeAutomationInternalPerformActionFunctionFactory() {
- return new FakeAutomationInternalPerformActionFunction();
-}
-
-// http://crbug.com/396353
-IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_GeneratedTree) {
- ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
- "automationInternal.enableTab",
- FakeAutomationInternalEnableTabFunctionFactory));
- ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
- "automationInternal.performAction",
- FakeAutomationInternalPerformActionFunctionFactory));
- ASSERT_TRUE(RunExtensionSubtest("automation/tests/generated",
- "generated_trees.html")) << message_;
-}
-
} // namespace extensions
diff --git a/chrome/browser/extensions/api/automation_internal/automation_event_router.cc b/chrome/browser/extensions/api/automation_internal/automation_event_router.cc
index c62a3fa..89ae0c7 100644
--- a/chrome/browser/extensions/api/automation_internal/automation_event_router.cc
+++ b/chrome/browser/extensions/api/automation_internal/automation_event_router.cc
@@ -62,7 +62,8 @@ void AutomationEventRouter::DispatchAccessibilityEvent(
content::RenderProcessHost* rph =
content::RenderProcessHost::FromID(listener.process_id);
- rph->Send(new ExtensionMsg_AccessibilityEvent(listener.routing_id, params));
+ rph->Send(new ExtensionMsg_AccessibilityEvent(listener.routing_id,
+ params));
}
}
@@ -93,7 +94,8 @@ void AutomationEventRouter::Register(
auto iter = std::find_if(
listeners_.begin(),
listeners_.end(),
- [listener_process_id, listener_routing_id](AutomationListener& item) {
+ [listener_process_id, listener_routing_id](
+ const AutomationListener& item) {
return (item.process_id == listener_process_id &&
item.routing_id == listener_routing_id);
});
@@ -132,8 +134,8 @@ void AutomationEventRouter::Observe(
std::remove_if(
listeners_.begin(),
listeners_.end(),
- [process_id](AutomationListener& item) {
- return item.process_id = process_id;
+ [process_id](const AutomationListener& item) {
+ return item.process_id == process_id;
});
}
diff --git a/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc b/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
index 5f2e8c8..58efba5 100644
--- a/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
+++ b/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
@@ -12,7 +12,6 @@
#include "chrome/browser/accessibility/ax_tree_id_registry.h"
#include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h"
#include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
-#include "chrome/browser/extensions/api/automation_internal/automation_util.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
@@ -23,12 +22,16 @@
#include "chrome/common/extensions/manifest_handlers/automation.h"
#include "content/public/browser/ax_event_notification_details.h"
#include "content/public/browser/browser_accessibility_state.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_plugin_guest_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/permissions/permissions_data.h"
@@ -45,6 +48,7 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
namespace extensions {
namespace {
+
const int kDesktopTreeID = 0;
const char kCannotRequestAutomationOnPage[] =
"Cannot request automation tree on url \"*\". "
@@ -193,24 +197,66 @@ class AutomationWebContentsObserver
void AccessibilityEventReceived(
const std::vector<content::AXEventNotificationDetails>& details)
override {
- automation_util::DispatchAccessibilityEventsToAutomation(
- details, browser_context_,
- web_contents()->GetContainerBounds().OffsetFromOrigin());
+ std::vector<content::AXEventNotificationDetails>::const_iterator iter =
+ details.begin();
+ for (; iter != details.end(); ++iter) {
+ const content::AXEventNotificationDetails& event = *iter;
+ int tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
+ event.process_id, event.routing_id);
+ ExtensionMsg_AccessibilityEventParams params;
+ params.tree_id = tree_id;
+ params.id = event.id;
+ params.event_type = event.event_type;
+ params.update.node_id_to_clear = event.node_id_to_clear;
+ params.update.nodes = event.nodes;
+ params.location_offset =
+ web_contents()->GetContainerBounds().OffsetFromOrigin();
+
+ for (size_t i = 0; i < params.update.nodes.size(); ++i) {
+ ui::AXNodeData& node = params.update.nodes[i];
+ if (node.HasBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST)) {
+ const auto& iter = event.node_to_browser_plugin_instance_id_map.find(
+ node.id);
+ if (iter != event.node_to_browser_plugin_instance_id_map.end()) {
+ int instance_id = iter->second;
+ content::BrowserPluginGuestManager* guest_manager =
+ browser_context_->GetGuestManager();
+ content::WebContents* guest_web_contents =
+ guest_manager->GetGuestByInstanceID(event.process_id,
+ instance_id);
+ if (guest_web_contents) {
+ content::RenderFrameHost* guest_rfh =
+ guest_web_contents->GetMainFrame();
+ int guest_tree_id =
+ AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
+ guest_rfh->GetProcess()->GetID(),
+ guest_rfh->GetRoutingID());
+ node.AddIntAttribute(ui::AX_ATTR_CHILD_TREE_ID, guest_tree_id);
+ }
+ }
+ }
+ }
+
+ AutomationEventRouter* router = AutomationEventRouter::GetInstance();
+ router->DispatchAccessibilityEvent(params);
+ }
}
void RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) override {
- automation_util::DispatchTreeDestroyedEventToAutomation(
+ int tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
render_frame_host->GetProcess()->GetID(),
- render_frame_host->GetRoutingID(),
+ render_frame_host->GetRoutingID());
+ AXTreeIDRegistry::GetInstance()->RemoveAXTreeID(tree_id);
+ AutomationEventRouter::GetInstance()->DispatchTreeDestroyedEvent(
+ tree_id,
browser_context_);
}
private:
friend class content::WebContentsUserData<AutomationWebContentsObserver>;
- AutomationWebContentsObserver(
- content::WebContents* web_contents)
+ explicit AutomationWebContentsObserver(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
browser_context_(web_contents->GetBrowserContext()) {}
@@ -245,6 +291,7 @@ AutomationInternalEnableTabFunction::Run() {
if (!contents)
return RespondNow(Error("No active tab"));
}
+
content::RenderFrameHost* rfh = contents->GetMainFrame();
if (!rfh)
return RespondNow(Error("Could not enable accessibility for active tab"));
@@ -256,6 +303,7 @@ AutomationInternalEnableTabFunction::Run() {
AutomationWebContentsObserver::CreateForWebContents(contents);
contents->EnableTreeOnlyAccessibilityMode();
+
int ax_tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
rfh->GetProcess()->GetID(), rfh->GetRoutingID());
@@ -270,11 +318,12 @@ AutomationInternalEnableTabFunction::Run() {
}
ExtensionFunction::ResponseAction AutomationInternalEnableFrameFunction::Run() {
-// TODO(dtseng): Limited to desktop tree for now pending out of proc iframes.
+ // TODO(dtseng): Limited to desktop tree for now pending out of proc iframes.
using api::automation_internal::EnableFrame::Params;
scoped_ptr<Params> params(Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
+
AXTreeIDRegistry::FrameID frame_id =
AXTreeIDRegistry::GetInstance()->GetFrameID(params->tree_id);
content::RenderFrameHost* rfh =
diff --git a/chrome/browser/extensions/api/automation_internal/automation_util.cc b/chrome/browser/extensions/api/automation_internal/automation_util.cc
deleted file mode 100644
index a16654b..0000000
--- a/chrome/browser/extensions/api/automation_internal/automation_util.cc
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2014 The Chromium 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 "chrome/browser/extensions/api/automation_internal/automation_util.h"
-
-#include <string>
-#include <utility>
-
-#include "base/values.h"
-#include "chrome/browser/accessibility/ax_tree_id_registry.h"
-#include "chrome/common/extensions/api/automation_internal.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_plugin_guest_manager.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_process_host.h"
-#include "content/public/browser/web_contents.h"
-#include "extensions/browser/event_router.h"
-#include "ui/accessibility/ax_enums.h"
-#include "ui/accessibility/ax_node_data.h"
-
-namespace extensions {
-
-namespace {
-
-void PopulateNodeData(const ui::AXNodeData& node_data,
- linked_ptr< api::automation_internal::AXNodeData>& out_node_data) {
- out_node_data->id = node_data.id;
- out_node_data->role = ToString(node_data.role);
-
- uint32 state_pos = 0, state_shifter = node_data.state;
- while (state_shifter) {
- if (state_shifter & 1) {
- out_node_data->state.additional_properties.SetBoolean(
- ToString(static_cast<ui::AXState>(state_pos)), true);
- }
- state_shifter = state_shifter >> 1;
- state_pos++;
- }
-
- out_node_data->location.left = node_data.location.x();
- out_node_data->location.top = node_data.location.y();
- out_node_data->location.width = node_data.location.width();
- out_node_data->location.height = node_data.location.height();
-
- if (!node_data.bool_attributes.empty()) {
- out_node_data->bool_attributes.reset(
- new api::automation_internal::AXNodeData::BoolAttributes());
- for (size_t i = 0; i < node_data.bool_attributes.size(); ++i) {
- std::pair<ui::AXBoolAttribute, bool> attr =
- node_data.bool_attributes[i];
- out_node_data->bool_attributes->additional_properties.SetBoolean(
- ToString(attr.first), attr.second);
- }
- }
-
- if (!node_data.float_attributes.empty()) {
- out_node_data->float_attributes.reset(
- new api::automation_internal::AXNodeData::FloatAttributes());
- for (size_t i = 0; i < node_data.float_attributes.size(); ++i) {
- std::pair<ui::AXFloatAttribute, float> attr =
- node_data.float_attributes[i];
- out_node_data->float_attributes->additional_properties.SetDouble(
- ToString(attr.first), attr.second);
- }
- }
-
- if (!node_data.html_attributes.empty()) {
- out_node_data->html_attributes.reset(
- new api::automation_internal::AXNodeData::HtmlAttributes());
- for (size_t i = 0; i < node_data.html_attributes.size(); ++i) {
- std::pair<std::string, std::string> attr = node_data.html_attributes[i];
- out_node_data->html_attributes->additional_properties.SetString(
- attr.first, attr.second);
- }
- }
-
- if (!node_data.int_attributes.empty()) {
- out_node_data->int_attributes.reset(
- new api::automation_internal::AXNodeData::IntAttributes());
- for (size_t i = 0; i < node_data.int_attributes.size(); ++i) {
- std::pair<ui::AXIntAttribute, int> attr = node_data.int_attributes[i];
- out_node_data->int_attributes->additional_properties.SetInteger(
- ToString(attr.first), attr.second);
- }
- }
-
- if (!node_data.intlist_attributes.empty()) {
- out_node_data->intlist_attributes.reset(
- new api::automation_internal::AXNodeData::IntlistAttributes());
- for (size_t i = 0; i < node_data.intlist_attributes.size(); ++i) {
- std::pair<ui::AXIntListAttribute, std::vector<int32> > attr =
- node_data.intlist_attributes[i];
- base::ListValue* intlist = new base::ListValue();
- for (size_t j = 0; j < attr.second.size(); ++j)
- intlist->AppendInteger(attr.second[j]);
- out_node_data->intlist_attributes->additional_properties.Set(
- ToString(attr.first), intlist);
- }
- }
-
- if (!node_data.string_attributes.empty()) {
- out_node_data->string_attributes.reset(
- new api::automation_internal::AXNodeData::StringAttributes());
- for (size_t i = 0; i < node_data.string_attributes.size(); ++i) {
- std::pair<ui::AXStringAttribute, std::string> attr =
- node_data.string_attributes[i];
- out_node_data->string_attributes->additional_properties.SetString(
- ToString(attr.first), attr.second);
- }
- }
-
- for (size_t i = 0; i < node_data.child_ids.size(); ++i) {
- out_node_data->child_ids.push_back(node_data.child_ids[i]);
- }
-}
-
-void DispatchEventInternal(content::BrowserContext* context,
- events::HistogramValue histogram_value,
- const std::string& event_name,
- scoped_ptr<base::ListValue> args) {
- if (context && EventRouter::Get(context)) {
- scoped_ptr<Event> event(
- new Event(histogram_value, event_name, args.Pass()));
- event->restrict_to_browser_context = context;
- EventRouter::Get(context)->BroadcastEvent(event.Pass());
- }
-}
-
-} // namespace
-
-namespace automation_util {
-
-void DispatchAccessibilityEventsToAutomation(
- const std::vector<content::AXEventNotificationDetails>& details,
- content::BrowserContext* browser_context,
- const gfx::Vector2d& location_offset) {
- using api::automation_internal::AXEventParams;
- using api::automation_internal::AXTreeUpdate;
-
- std::vector<content::AXEventNotificationDetails>::const_iterator iter =
- details.begin();
- for (; iter != details.end(); ++iter) {
- const content::AXEventNotificationDetails& event = *iter;
-
- AXEventParams ax_event_params;
- ax_event_params.tree_id =
- AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(event.process_id,
- event.routing_id);
- ax_event_params.event_type = ToString(iter->event_type);
- ax_event_params.target_id = event.id;
-
- AXTreeUpdate& ax_tree_update = ax_event_params.update;
- ax_tree_update.node_id_to_clear = event.node_id_to_clear;
- for (size_t i = 0; i < event.nodes.size(); ++i) {
- ui::AXNodeData src = event.nodes[i];
- src.location.Offset(location_offset);
- linked_ptr<api::automation_internal::AXNodeData> out_node(
- new api::automation_internal::AXNodeData());
- PopulateNodeData(src, out_node);
- if (src.HasBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST)) {
- const auto& iter = event.node_to_browser_plugin_instance_id_map.find(
- src.id);
- if (iter != event.node_to_browser_plugin_instance_id_map.end()) {
- int instance_id = iter->second;
- content::BrowserPluginGuestManager* guest_manager =
- browser_context->GetGuestManager();
- content::WebContents* guest_web_contents =
- guest_manager->GetGuestByInstanceID(event.process_id,
- instance_id);
- if (guest_web_contents) {
- content::RenderFrameHost* guest_rfh =
- guest_web_contents->GetMainFrame();
- int guest_tree_id =
- AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
- guest_rfh->GetProcess()->GetID(),
- guest_rfh->GetRoutingID());
- out_node->int_attributes->additional_properties.SetInteger(
- ToString(ui::AX_ATTR_CHILD_TREE_ID),
- guest_tree_id);
- }
- }
- }
- ax_tree_update.nodes.push_back(out_node);
- }
-
- // TODO(dtseng/aboxhall): Why are we sending only one update at a time? We
- // should match the behavior from renderer -> browser and send a
- // collection of tree updates over (to the extension); see
- // |AccessibilityHostMsg_EventParams| and |AccessibilityHostMsg_Events|.
- DispatchEventInternal(
- browser_context, events::AUTOMATION_INTERNAL_ON_ACCESSIBILITY_EVENT,
- api::automation_internal::OnAccessibilityEvent::kEventName,
- api::automation_internal::OnAccessibilityEvent::Create(
- ax_event_params));
- }
-}
-
-void DispatchTreeDestroyedEventToAutomation(
- int process_id,
- int routing_id,
- content::BrowserContext* browser_context) {
- int tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
- process_id, routing_id);
- DispatchEventInternal(
- browser_context,
- events::AUTOMATION_INTERNAL_ON_ACCESSIBILITY_TREE_DESTROYED,
- api::automation_internal::OnAccessibilityTreeDestroyed::kEventName,
- api::automation_internal::OnAccessibilityTreeDestroyed::Create(tree_id));
- AXTreeIDRegistry::GetInstance()->RemoveAXTreeID(tree_id);
-}
-
-} // namespace automation_util
-
-} // namespace extensions
diff --git a/chrome/browser/extensions/api/automation_internal/automation_util.h b/chrome/browser/extensions/api/automation_internal/automation_util.h
deleted file mode 100644
index 218038b..0000000
--- a/chrome/browser/extensions/api/automation_internal/automation_util.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2014 The Chromium 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 CHROME_BROWSER_EXTENSIONS_API_AUTOMATION_INTERNAL_AUTOMATION_UTIL_H_
-#define CHROME_BROWSER_EXTENSIONS_API_AUTOMATION_INTERNAL_AUTOMATION_UTIL_H_
-
-#include <vector>
-
-#include "chrome/common/extensions/api/automation_internal.h"
-#include "content/public/browser/ax_event_notification_details.h"
-
-namespace content {
-class BrowserContext;
-} // namespace content
-
-namespace ui {
-struct AXNodeData;
-} // namespace ui
-
-namespace extensions {
-
-// Shared utility functions for the Automation API.
-namespace automation_util {
-
-// Dispatch events through the Automation API making any necessary conversions
-// from accessibility to Automation types.
-void DispatchAccessibilityEventsToAutomation(
- const std::vector<content::AXEventNotificationDetails>& details,
- content::BrowserContext* browser_context,
- const gfx::Vector2d& location_offset);
-
-void DispatchTreeDestroyedEventToAutomation(
- int process_id,
- int routing_id,
- content::BrowserContext* browser_context);
-} // namespace automation_util
-
-} // namespace extensions
-
-#endif // CHROME_BROWSER_EXTENSIONS_API_AUTOMATION_INTERNAL_AUTOMATION_UTIL_H_
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
index 581f90c..8179d5b 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
@@ -440,7 +440,7 @@ Output.RULES = {
},
textField: {
speak: '$name $value $if(' +
- '$type, @input_type_+$type, @input_type_text)',
+ '$inputType, @input_type_+$inputType, @input_type_text)',
braille: ''
},
toolbar: {
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
index 5bc04e7..d1368d5 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
@@ -8,8 +8,9 @@
#include "base/memory/singleton.h"
#include "chrome/browser/browser_process.h"
-#include "chrome/browser/extensions/api/automation_internal/automation_util.h"
+#include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/extensions/chrome_extension_messages.h"
#include "content/public/browser/ax_event_notification_details.h"
#include "content/public/browser/browser_context.h"
#include "ui/aura/window.h"
@@ -19,6 +20,7 @@
#include "ui/views/widget/widget.h"
using content::BrowserContext;
+using extensions::AutomationEventRouter;
// static
AutomationManagerAura* AutomationManagerAura::GetInstance() {
@@ -64,20 +66,7 @@ void AutomationManagerAura::HandleEvent(BrowserContext* context,
views::AXAuraObjWrapper* aura_obj =
views::AXAuraObjCache::GetInstance()->GetOrCreate(view);
-
- if (processing_events_) {
- pending_events_.push_back(std::make_pair(aura_obj, event_type));
- return;
- }
-
- processing_events_ = true;
SendEvent(context, aura_obj, event_type);
-
- for (size_t i = 0; i < pending_events_.size(); ++i)
- SendEvent(context, pending_events_[i].first, pending_events_[i].second);
-
- processing_events_ = false;
- pending_events_.clear();
}
void AutomationManagerAura::HandleAlert(content::BrowserContext* context,
@@ -132,21 +121,28 @@ void AutomationManagerAura::ResetSerializer() {
void AutomationManagerAura::SendEvent(BrowserContext* context,
views::AXAuraObjWrapper* aura_obj,
ui::AXEvent event_type) {
- ui::AXTreeUpdate update;
- current_tree_serializer_->SerializeChanges(aura_obj, &update);
-
- // Route this event to special process/routing ids recognized by the
- // Automation API as the desktop tree.
- // TODO(dtseng): Would idealy define these special desktop constants in idl.
- content::AXEventNotificationDetails detail(
- update.node_id_to_clear, update.nodes, event_type, aura_obj->GetID(),
- std::map<int32, int>(),
- 0, /* process_id */
- 0 /* routing_id */);
- std::vector<content::AXEventNotificationDetails> details;
- details.push_back(detail);
- extensions::automation_util::DispatchAccessibilityEventsToAutomation(
- details, context, gfx::Vector2d());
+ if (processing_events_) {
+ pending_events_.push_back(std::make_pair(aura_obj, event_type));
+ return;
+ }
+ processing_events_ = true;
+
+ ExtensionMsg_AccessibilityEventParams params;
+ current_tree_serializer_->SerializeChanges(aura_obj, &params.update);
+ params.tree_id = 0;
+ params.id = aura_obj->GetID();
+ params.event_type = event_type;
+ AutomationEventRouter* router = AutomationEventRouter::GetInstance();
+ router->DispatchAccessibilityEvent(params);
+
+ processing_events_ = false;
+ auto pending_events_copy = pending_events_;
+ pending_events_.clear();
+ for (size_t i = 0; i < pending_events_copy.size(); ++i) {
+ SendEvent(context,
+ pending_events_copy[i].first,
+ pending_events_copy[i].second);
+ }
}
void AutomationManagerAura::OnNativeFocusChanged(aura::Window* focused_now) {
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 9602ac5..77f2c89 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -138,8 +138,6 @@
'browser/extensions/api/automation_internal/automation_event_router.h',
'browser/extensions/api/automation_internal/automation_internal_api.cc',
'browser/extensions/api/automation_internal/automation_internal_api.h',
- 'browser/extensions/api/automation_internal/automation_util.cc',
- 'browser/extensions/api/automation_internal/automation_util.h',
'browser/extensions/api/autotest_private/autotest_private_api.cc',
'browser/extensions/api/autotest_private/autotest_private_api.h',
'browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc',
diff --git a/chrome/common/extensions/api/automation.idl b/chrome/common/extensions/api/automation.idl
index 51cdfe7..b49c3a5 100644
--- a/chrome/common/extensions/api/automation.idl
+++ b/chrome/common/extensions/api/automation.idl
@@ -318,8 +318,6 @@
// The role of this node.
automation.RoleType role;
- // TODO(aboxhall): expose states as mixins instead
-
// The $(ref:automation.StateType)s describing this node.
object state;
@@ -370,96 +368,21 @@
// name, via the $(ref:automation.AutomationNode.name) attribute.
AutomationNode[] labelledBy;
- // The nodes, if any, which are to be considered children of this node but
- // are not children in the DOM tree.
- AutomationNode[] owns;
-
- // TODO(aboxhall): Make this private?
-
- // A collection of this node's other attributes.
- object? attributes;
-
- // The index of this node in its parent node's list of children. If this is
- // the root node, this will be undefined.
- long? indexInParent;
-
- AutomationNode[] children;
- AutomationNode parent;
- AutomationNode firstChild;
- AutomationNode lastChild;
- AutomationNode previousSibling;
- AutomationNode nextSibling;
-
- // Does the default action based on this node's role. This is generally
- // the same action that would result from clicking the node such as
- // expanding a treeitem, toggling a checkbox, selecting a radiobutton,
- // or activating a button.
- static void doDefault();
-
- // Places focus on this node.
- static void focus();
-
- // Scrolls this node to make it visible.
- static void makeVisible();
-
- // Sets selection within a text field.
- static void setSelection(long startIndex, long endIndex);
-
- // Shows the context menu resulting from a right click on this node.
- static void showContextMenu();
-
- // Adds a listener for the given event type and event phase.
- static void addEventListener(
- EventType eventType, AutomationListener listener, boolean capture);
-
- // Removes a listener for the given event type and event phase.
- static void removeEventListener(
- EventType eventType, AutomationListener listener, boolean capture);
-
- // Gets the first node in this node's subtree which matches the given CSS
- // selector and is within the same DOM context.
- //
- // If this node doesn't correspond directly with an HTML node in the DOM,
- // querySelector will be run on this node's nearest HTML node ancestor. Note
- // that this may result in the query returning a node which is not a
- // descendant of this node.
- //
- // If the selector matches a node which doesn't directly correspond to an
- // automation node (for example an element within an ARIA widget, where the
- // ARIA widget forms one node of the automation tree, or an element which
- // is hidden from accessibility via hiding it using CSS or using
- // aria-hidden), this will return the nearest ancestor which does correspond
- // to an automation node.
- static void domQuerySelector(DOMString selector, QueryCallback callback);
-
- // Finds the first AutomationNode in this node's subtree which matches the
- // given search parameters.
- static AutomationNode find(FindParams params);
-
- // Finds all the AutomationNodes in this node's subtree which matches the
- // given search parameters.
- static AutomationNode[] findAll(FindParams params);
-
- // Returns whether this node matches the given $(ref:automation.FindParams).
- static boolean matches(FindParams params);
- };
-
- dictionary ActiveDescendantMixin {
// The node referred to by <code>aria-activedescendant</code>, where
// applicable
AutomationNode activedescendant;
- };
- // Attributes which are mixed in to an AutomationNode if it is a link.
- dictionary LinkMixins {
- // TODO(aboxhall): Add visited state
+ //
+ // Link attributes.
+ //
// The URL that this link will navigate to.
DOMString url;
- };
- // Attributes which are mixed in to an AutomationNode if it is a document.
- dictionary DocumentMixins {
+ //
+ // Document attributes.
+ //
+
// The URL of this document.
DOMString docUrl;
@@ -471,23 +394,22 @@
// The proportion (out of 1.0) that this doc has completed loading.
double docLoadingProgress;
- };
- // TODO(aboxhall): document ScrollableMixins (e.g. what is scrollXMin? is it
- // ever not 0?)
+ //
+ // Scrollable container attributes.
+ //
- // Attributes which are mixed in to an AutomationNode if it is scrollable.
- dictionary ScrollableMixins {
long scrollX;
long scrollXMin;
long scrollXMax;
long scrollY;
long scrollYMin;
long scrollYMax;
- };
- // Attributes which are mixed in to an AutomationNode if it is editable text.
- dictionary EditableTextMixins {
+ //
+ // Editable text field attributes.
+ //
+
// The character index of the start of the selection within this editable
// text element; -1 if no selection.
long textSelStart;
@@ -498,10 +420,11 @@
// The input type, like email or number.
DOMString textInputType;
- };
- // Attributes which are mixed in to an AutomationNode if it is a range.
- dictionary RangeMixins {
+ //
+ // Range attributes.
+ //
+
// The current value for this range.
double valueForRange;
@@ -510,21 +433,21 @@
// The maximum possible value for this range.
double maxValueForRange;
- };
- // TODO(aboxhall): live region mixins.
+ //
+ // Table attributes.
+ //
- // Attributes which are mixed in to an AutomationNode if it is a table.
- dictionary TableMixins {
// The number of rows in this table.
long tableRowCount;
// The number of columns in this table.
long tableColumnCount;
- };
- // Attributes which are mixed in to an AutomationNode if it is a table cell.
- dictionary TableCellMixins {
+ //
+ // Table cell attributes.
+ //
+
// The zero-based index of the column that this cell is in.
long tableCellColumnIndex;
@@ -536,6 +459,75 @@
// The number of rows that this cell spans (default is 1).
long tableCellRowSpan;
+
+ //
+ // Walking the tree.
+ //
+
+ AutomationNode[] children;
+ AutomationNode parent;
+ AutomationNode firstChild;
+ AutomationNode lastChild;
+ AutomationNode previousSibling;
+ AutomationNode nextSibling;
+
+ // The index of this node in its parent node's list of children. If this is
+ // the root node, this will be undefined.
+ long? indexInParent;
+
+ //
+ // Actions.
+ //
+
+ // Does the default action based on this node's role. This is generally
+ // the same action that would result from clicking the node such as
+ // expanding a treeitem, toggling a checkbox, selecting a radiobutton,
+ // or activating a button.
+ static void doDefault();
+
+ // Places focus on this node.
+ static void focus();
+
+ // Scrolls this node to make it visible.
+ static void makeVisible();
+
+ // Sets selection within a text field.
+ static void setSelection(long startIndex, long endIndex);
+
+ // Adds a listener for the given event type and event phase.
+ static void addEventListener(
+ EventType eventType, AutomationListener listener, boolean capture);
+
+ // Removes a listener for the given event type and event phase.
+ static void removeEventListener(
+ EventType eventType, AutomationListener listener, boolean capture);
+
+ // Gets the first node in this node's subtree which matches the given CSS
+ // selector and is within the same DOM context.
+ //
+ // If this node doesn't correspond directly with an HTML node in the DOM,
+ // querySelector will be run on this node's nearest HTML node ancestor. Note
+ // that this may result in the query returning a node which is not a
+ // descendant of this node.
+ //
+ // If the selector matches a node which doesn't directly correspond to an
+ // automation node (for example an element within an ARIA widget, where the
+ // ARIA widget forms one node of the automation tree, or an element which
+ // is hidden from accessibility via hiding it using CSS or using
+ // aria-hidden), this will return the nearest ancestor which does correspond
+ // to an automation node.
+ static void domQuerySelector(DOMString selector, QueryCallback callback);
+
+ // Finds the first AutomationNode in this node's subtree which matches the
+ // given search parameters.
+ static AutomationNode find(FindParams params);
+
+ // Finds all the AutomationNodes in this node's subtree which matches the
+ // given search parameters.
+ static AutomationNode[] findAll(FindParams params);
+
+ // Returns whether this node matches the given $(ref:automation.FindParams).
+ static boolean matches(FindParams params);
};
// Called when the <code>AutomationNode</code> for the page is available.
diff --git a/chrome/common/extensions/api/automation_internal.idl b/chrome/common/extensions/api/automation_internal.idl
index b290c63..6f568cf 100644
--- a/chrome/common/extensions/api/automation_internal.idl
+++ b/chrome/common/extensions/api/automation_internal.idl
@@ -6,46 +6,10 @@
// essentially a translation of the internal accessibility tree update system
// into an extension API.
namespace automationInternal {
- dictionary Rect {
- long left;
- long top;
- long width;
- long height;
- };
-
- // A compact representation of the accessibility information for a
- // single web object, in a form that can be serialized and sent from
- // one process to another.
- // See ui/accessibility/ax_node_data.h
- dictionary AXNodeData {
- long id;
- DOMString role;
- object state;
- Rect location;
-
- object? boolAttributes;
- object? floatAttributes;
- object? htmlAttributes;
- object? intAttributes;
- object? intlistAttributes;
- object? stringAttributes;
- long[] childIds;
- };
-
- dictionary AXTreeUpdate {
- // ID of the node, if any, which should be invalidated before the new data
- // is applied.
- long nodeIdToClear;
-
- // A vector of nodes to update according to the rules described in
- // ui/accessibility/ax_tree_update.h.
- AXNodeData[] nodes;
- };
-
// Data for an accessibility event and/or an atomic change to an accessibility
// tree. See ui/accessibility/ax_tree_update.h for an extended explanation of
// the tree update format.
- dictionary AXEventParams {
+ [nocompile] dictionary AXEventParams {
// The tree id of the web contents that this update is for.
long treeID;
@@ -54,10 +18,6 @@ namespace automationInternal {
// The type of event that this update represents.
DOMString eventType;
-
- // Serialized changes to the tree structure and node data that should be
- // applied before processing the event.
- AXTreeUpdate update;
};
// All possible actions that can be performed on automation nodes.
@@ -133,5 +93,7 @@ namespace automationInternal {
static void onAccessibilityEvent(AXEventParams update);
static void onAccessibilityTreeDestroyed(long treeID);
+
+ static void onTreeChange(long treeID, long nodeID, DOMString changeType);
};
};
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
index 7f70eb5..632d07d 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -6,10 +6,10 @@
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
+#include "base/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/common/extensions/chrome_extension_messages.h"
#include "chrome/common/extensions/manifest_handlers/automation.h"
-#include "content/public/child/v8_value_converter.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
@@ -40,6 +40,9 @@ v8::Local<v8::Object> ToEnumObject(v8::Isolate* isolate,
namespace extensions {
+TreeCache::TreeCache() {}
+TreeCache::~TreeCache() {}
+
class AutomationMessageFilter : public IPC::MessageFilter {
public:
explicit AutomationMessageFilter(AutomationInternalCustomBindings* owner)
@@ -47,6 +50,7 @@ class AutomationMessageFilter : public IPC::MessageFilter {
removed_(false) {
DCHECK(owner);
content::RenderThread::Get()->AddFilter(this);
+ task_runner_ = base::ThreadTaskRunnerHandle::Get();
}
void Detach() {
@@ -56,10 +60,15 @@ class AutomationMessageFilter : public IPC::MessageFilter {
// IPC::MessageFilter
bool OnMessageReceived(const IPC::Message& message) override {
- if (owner_)
- return owner_->OnMessageReceived(message);
- else
- return false;
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &AutomationMessageFilter::OnMessageReceivedOnRenderThread,
+ this, message));
+
+ // Always return false in case there are multiple
+ // AutomationInternalCustomBindings instances attached to the same thread.
+ return false;
}
void OnFilterRemoved() override {
@@ -67,6 +76,11 @@ class AutomationMessageFilter : public IPC::MessageFilter {
}
private:
+ void OnMessageReceivedOnRenderThread(const IPC::Message& message) {
+ if (owner_)
+ owner_->OnMessageReceived(message);
+ }
+
~AutomationMessageFilter() override {
Remove();
}
@@ -80,6 +94,7 @@ private:
AutomationInternalCustomBindings* owner_;
bool removed_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter);
};
@@ -89,35 +104,46 @@ AutomationInternalCustomBindings::AutomationInternalCustomBindings(
// It's safe to use base::Unretained(this) here because these bindings
// will only be called on a valid AutomationInternalCustomBindings instance
// and none of the functions have any side effects.
- RouteFunction(
- "IsInteractPermitted",
- base::Bind(&AutomationInternalCustomBindings::IsInteractPermitted,
- base::Unretained(this)));
- RouteFunction(
- "GetSchemaAdditions",
- base::Bind(&AutomationInternalCustomBindings::GetSchemaAdditions,
- base::Unretained(this)));
- RouteFunction(
- "GetRoutingID",
- base::Bind(&AutomationInternalCustomBindings::GetRoutingID,
- base::Unretained(this)));
-
- message_filter_ = new AutomationMessageFilter(this);
+ #define ROUTE_FUNCTION(FN) \
+ RouteFunction(#FN, \
+ base::Bind(&AutomationInternalCustomBindings::FN, \
+ base::Unretained(this)))
+
+ ROUTE_FUNCTION(IsInteractPermitted);
+ ROUTE_FUNCTION(GetSchemaAdditions);
+ ROUTE_FUNCTION(GetRoutingID);
+ ROUTE_FUNCTION(StartCachingAccessibilityTrees);
+ ROUTE_FUNCTION(DestroyAccessibilityTree);
+ ROUTE_FUNCTION(GetRootID);
+ ROUTE_FUNCTION(GetParentID);
+ ROUTE_FUNCTION(GetChildCount);
+ ROUTE_FUNCTION(GetChildIDAtIndex);
+ ROUTE_FUNCTION(GetIndexInParent);
+ ROUTE_FUNCTION(GetState);
+ ROUTE_FUNCTION(GetRole);
+ ROUTE_FUNCTION(GetLocation);
+ ROUTE_FUNCTION(GetStringAttribute);
+ ROUTE_FUNCTION(GetBoolAttribute);
+ ROUTE_FUNCTION(GetIntAttribute);
+ ROUTE_FUNCTION(GetFloatAttribute);
+ ROUTE_FUNCTION(GetIntListAttribute);
+ ROUTE_FUNCTION(GetHtmlAttribute);
+
+ #undef ROUTE_FUNCTION
}
AutomationInternalCustomBindings::~AutomationInternalCustomBindings() {
- message_filter_->Detach();
+ if (message_filter_)
+ message_filter_->Detach();
+ STLDeleteContainerPairSecondPointers(tree_id_to_tree_cache_map_.begin(),
+ tree_id_to_tree_cache_map_.end());
}
-bool AutomationInternalCustomBindings::OnMessageReceived(
+void AutomationInternalCustomBindings::OnMessageReceived(
const IPC::Message& message) {
IPC_BEGIN_MESSAGE_MAP(AutomationInternalCustomBindings, message)
IPC_MESSAGE_HANDLER(ExtensionMsg_AccessibilityEvent, OnAccessibilityEvent)
IPC_END_MESSAGE_MAP()
-
- // Always return false in case there are multiple
- // AutomationInternalCustomBindings instances attached to the same thread.
- return false;
}
void AutomationInternalCustomBindings::IsInteractPermitted(
@@ -136,6 +162,12 @@ void AutomationInternalCustomBindings::GetRoutingID(
args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), routing_id));
}
+void AutomationInternalCustomBindings::StartCachingAccessibilityTrees(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (!message_filter_)
+ message_filter_ = new AutomationMessageFilter(this);
+}
+
void AutomationInternalCustomBindings::GetSchemaAdditions(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Local<v8::Object> additions = v8::Object::New(GetIsolate());
@@ -159,9 +191,438 @@ void AutomationInternalCustomBindings::GetSchemaAdditions(
args.GetReturnValue().Set(additions);
}
+void AutomationInternalCustomBindings::DestroyAccessibilityTree(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1 || !args[0]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return;
+ }
+
+ int tree_id = args[0]->Int32Value();
+ auto iter = tree_id_to_tree_cache_map_.find(tree_id);
+ if (iter == tree_id_to_tree_cache_map_.end())
+ return;
+
+ TreeCache* cache = iter->second;
+ tree_id_to_tree_cache_map_.erase(tree_id);
+ axtree_to_tree_cache_map_.erase(&cache->tree);
+ delete cache;
+}
+
+//
+// Access the cached accessibility trees and properties of their nodes.
+//
+
+void AutomationInternalCustomBindings::GetRootID(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1 || !args[0]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return;
+ }
+
+ int tree_id = args[0]->Int32Value();
+ const auto iter = tree_id_to_tree_cache_map_.find(tree_id);
+ if (iter == tree_id_to_tree_cache_map_.end())
+ return;
+
+ TreeCache* cache = iter->second;
+ int root_id = cache->tree.root()->id();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), root_id));
+}
+
+void AutomationInternalCustomBindings::GetParentID(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ if (!node->parent())
+ return;
+
+ int parent_id = node->parent()->id();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), parent_id));
+}
+
+void AutomationInternalCustomBindings::GetChildCount(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ int child_count = node->child_count();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), child_count));
+}
+
+void AutomationInternalCustomBindings::GetChildIDAtIndex(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() < 3 || !args[2]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return;
+ }
+
+ ui::AXNode* node = nullptr;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ int index = args[2]->Int32Value();
+ if (index < 0 || index >= node->child_count())
+ return;
+
+ int child_id = node->children()[index]->id();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), child_id));
+}
+
+void AutomationInternalCustomBindings::GetIndexInParent(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ int index_in_parent = node->index_in_parent();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), index_in_parent));
+}
+
+void AutomationInternalCustomBindings::GetState(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ v8::Local<v8::Object> state(v8::Object::New(GetIsolate()));
+ uint32 state_pos = 0, state_shifter = node->data().state;
+ while (state_shifter) {
+ if (state_shifter & 1) {
+ std::string key = ToString(static_cast<ui::AXState>(state_pos));
+ state->Set(CreateV8String(key),
+ v8::Boolean::New(GetIsolate(), true));
+ }
+ state_shifter = state_shifter >> 1;
+ state_pos++;
+ }
+
+ args.GetReturnValue().Set(state);
+}
+
+void AutomationInternalCustomBindings::GetRole(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ std::string role_name = ui::ToString(node->data().role);
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(GetIsolate(), role_name.c_str()));
+}
+
+void AutomationInternalCustomBindings::GetLocation(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ TreeCache* cache;
+ ui::AXNode* node = nullptr;
+ if (!GetNodeHelper(args, &cache, &node))
+ return;
+
+ v8::Local<v8::Object> location_obj(v8::Object::New(GetIsolate()));
+ gfx::Rect location = node->data().location;
+ location.Offset(cache->location_offset);
+ location_obj->Set(CreateV8String("left"),
+ v8::Integer::New(GetIsolate(), location.x()));
+ location_obj->Set(CreateV8String("top"),
+ v8::Integer::New(GetIsolate(), location.y()));
+ location_obj->Set(CreateV8String("width"),
+ v8::Integer::New(GetIsolate(), location.width()));
+ location_obj->Set(CreateV8String("height"),
+ v8::Integer::New(GetIsolate(), location.height()));
+ args.GetReturnValue().Set(location_obj);
+}
+
+void AutomationInternalCustomBindings::GetStringAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXStringAttribute attribute = ui::ParseAXStringAttribute(attribute_name);
+ std::string attr_value;
+ if (!node->data().GetStringAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(GetIsolate(), attr_value.c_str()));
+}
+
+void AutomationInternalCustomBindings::GetBoolAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXBoolAttribute attribute = ui::ParseAXBoolAttribute(attribute_name);
+ bool attr_value;
+ if (!node->data().GetBoolAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(v8::Boolean::New(GetIsolate(), attr_value));
+}
+
+void AutomationInternalCustomBindings::GetIntAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXIntAttribute attribute = ui::ParseAXIntAttribute(attribute_name);
+ int attr_value;
+ if (!node->data().GetIntAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), attr_value));
+}
+
+void AutomationInternalCustomBindings::GetFloatAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXFloatAttribute attribute = ui::ParseAXFloatAttribute(attribute_name);
+ float attr_value;
+
+ if (!node->data().GetFloatAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(v8::Number::New(GetIsolate(), attr_value));
+}
+
+void AutomationInternalCustomBindings::GetIntListAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXIntListAttribute attribute =
+ ui::ParseAXIntListAttribute(attribute_name);
+ if (!node->data().HasIntListAttribute(attribute))
+ return;
+ const std::vector<int32>& attr_value =
+ node->data().GetIntListAttribute(attribute);
+
+ v8::Local<v8::Array> result(v8::Array::New(GetIsolate(), attr_value.size()));
+ for (size_t i = 0; i < attr_value.size(); ++i)
+ result->Set(static_cast<uint32>(i),
+ v8::Integer::New(GetIsolate(), attr_value[i]));
+ args.GetReturnValue().Set(result);
+}
+
+void AutomationInternalCustomBindings::GetHtmlAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node = nullptr;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ std::string attr_value;
+ if (!node->data().GetHtmlAttribute(attribute_name.c_str(), &attr_value))
+ return;
+
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(GetIsolate(), attr_value.c_str()));
+}
+
+//
+// Helper functions.
+//
+
+void AutomationInternalCustomBindings::ThrowInvalidArgumentsException(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ GetIsolate()->ThrowException(
+ v8::String::NewFromUtf8(
+ GetIsolate(),
+ "Invalid arguments to AutomationInternalCustomBindings function",
+ v8::NewStringType::kNormal).ToLocalChecked());
+
+ LOG(FATAL)
+ << "Invalid arguments to AutomationInternalCustomBindings function"
+ << context()->GetStackTraceAsString();
+}
+
+bool AutomationInternalCustomBindings::GetNodeHelper(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ TreeCache** out_cache,
+ ui::AXNode** out_node) {
+ if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return false;
+ }
+
+ int tree_id = args[0]->Int32Value();
+ int node_id = args[1]->Int32Value();
+
+ const auto iter = tree_id_to_tree_cache_map_.find(tree_id);
+ if (iter == tree_id_to_tree_cache_map_.end())
+ return false;
+
+ TreeCache* cache = iter->second;
+ ui::AXNode* node = cache->tree.GetFromId(node_id);
+
+ if (out_cache)
+ *out_cache = cache;
+ if (out_node)
+ *out_node = node;
+
+ return node != nullptr;
+}
+
+bool AutomationInternalCustomBindings::GetAttributeHelper(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ ui::AXNode** out_node,
+ std::string* out_attribute_name) {
+ if (args.Length() != 3 ||
+ !args[2]->IsString()) {
+ ThrowInvalidArgumentsException(args);
+ return false;
+ }
+
+ TreeCache* cache = nullptr;
+ if (!GetNodeHelper(args, &cache, out_node))
+ return false;
+
+ *out_attribute_name = *v8::String::Utf8Value(args[2]);
+ return true;
+}
+
+v8::Local<v8::Value> AutomationInternalCustomBindings::CreateV8String(
+ const char* str) {
+ return v8::String::NewFromUtf8(
+ GetIsolate(), str, v8::String::kNormalString, strlen(str));
+}
+
+v8::Local<v8::Value> AutomationInternalCustomBindings::CreateV8String(
+ const std::string& str) {
+ return v8::String::NewFromUtf8(
+ GetIsolate(), str.c_str(), v8::String::kNormalString, str.length());
+}
+
+//
+// Handle accessibility events from the browser process.
+//
+
void AutomationInternalCustomBindings::OnAccessibilityEvent(
const ExtensionMsg_AccessibilityEventParams& params) {
- // TODO(dmazzoni): finish implementing this.
+ int tree_id = params.tree_id;
+ TreeCache* cache;
+ auto iter = tree_id_to_tree_cache_map_.find(tree_id);
+ if (iter == tree_id_to_tree_cache_map_.end()) {
+ cache = new TreeCache();
+ cache->tab_id = -1;
+ cache->tree_id = params.tree_id;
+ cache->tree.SetDelegate(this);
+ tree_id_to_tree_cache_map_.insert(std::make_pair(tree_id, cache));
+ axtree_to_tree_cache_map_.insert(std::make_pair(&cache->tree, cache));
+ } else {
+ cache = iter->second;
+ }
+
+ cache->location_offset = params.location_offset;
+ if (!cache->tree.Unserialize(params.update)) {
+ LOG(FATAL) << cache->tree.error();
+ return;
+ }
+
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Context::Scope context_scope(context()->v8_context());
+ v8::Local<v8::Array> args(v8::Array::New(GetIsolate(), 1U));
+ v8::Local<v8::Object> event_params(v8::Object::New(GetIsolate()));
+ event_params->Set(CreateV8String("treeID"),
+ v8::Integer::New(GetIsolate(), params.tree_id));
+ event_params->Set(CreateV8String("targetID"),
+ v8::Integer::New(GetIsolate(), params.id));
+ event_params->Set(CreateV8String("eventType"),
+ CreateV8String(ToString(params.event_type)));
+ args->Set(0U, event_params);
+ context()->DispatchEvent("automationInternal.onAccessibilityEvent", args);
+}
+
+void AutomationInternalCustomBindings::OnNodeWillBeDeleted(ui::AXTree* tree,
+ ui::AXNode* node) {
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_NODEREMOVED,
+ tree, node);
+}
+
+void AutomationInternalCustomBindings::OnSubtreeWillBeDeleted(
+ ui::AXTree* tree,
+ ui::AXNode* node) {
+ // This isn't strictly needed, as OnNodeWillBeDeleted will already be
+ // called. We could send a JS event for this only if it turns out to
+ // be needed for something.
+}
+
+void AutomationInternalCustomBindings::OnNodeCreated(ui::AXTree* tree,
+ ui::AXNode* node) {
+ // Not needed, this is called in the middle of an update so it's not
+ // safe to trigger JS from here. Wait for the notification in
+ // OnAtomicUpdateFinished instead.
+}
+
+void AutomationInternalCustomBindings::OnNodeChanged(ui::AXTree* tree,
+ ui::AXNode* node) {
+ // Not needed, this is called in the middle of an update so it's not
+ // safe to trigger JS from here. Wait for the notification in
+ // OnAtomicUpdateFinished instead.
+}
+
+void AutomationInternalCustomBindings::OnAtomicUpdateFinished(
+ ui::AXTree* tree,
+ bool root_changed,
+ const std::vector<ui::AXTreeDelegate::Change>& changes) {
+ auto iter = axtree_to_tree_cache_map_.find(tree);
+ if (iter == axtree_to_tree_cache_map_.end())
+ return;
+
+ for (auto change : changes) {
+ ui::AXNode* node = change.node;
+ switch (change.type) {
+ case NODE_CREATED:
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_NODECREATED,
+ tree, node);
+ break;
+ case SUBTREE_CREATED:
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_SUBTREECREATED,
+ tree, node);
+ break;
+ case NODE_CHANGED:
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_NODECHANGED,
+ tree, node);
+ break;
+ }
+ }
+}
+
+void AutomationInternalCustomBindings::SendTreeChangeEvent(
+ api::automation::TreeChangeType change_type,
+ ui::AXTree* tree,
+ ui::AXNode* node) {
+ auto iter = axtree_to_tree_cache_map_.find(tree);
+ if (iter == axtree_to_tree_cache_map_.end())
+ return;
+
+ int tree_id = iter->second->tree_id;
+
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Context::Scope context_scope(context()->v8_context());
+ v8::Local<v8::Array> args(v8::Array::New(GetIsolate(), 3U));
+ args->Set(0U, v8::Integer::New(GetIsolate(), tree_id));
+ args->Set(1U, v8::Integer::New(GetIsolate(), node->id()));
+ args->Set(2U, CreateV8String(ToString(change_type)));
+ context()->DispatchEvent("automationInternal.onTreeChange", args);
}
} // namespace extensions
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.h b/chrome/renderer/extensions/automation_internal_custom_bindings.h
index 1bc8a79..c4f2cd2 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.h
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.h
@@ -6,6 +6,7 @@
#define CHROME_RENDERER_EXTENSIONS_AUTOMATION_INTERNAL_CUSTOM_BINDINGS_H_
#include "base/compiler_specific.h"
+#include "chrome/common/extensions/api/automation.h"
#include "extensions/renderer/object_backed_native_handler.h"
#include "ipc/ipc_message.h"
#include "ui/accessibility/ax_tree.h"
@@ -17,15 +18,27 @@ namespace extensions {
class AutomationMessageFilter;
+struct TreeCache {
+ TreeCache();
+ ~TreeCache();
+
+ int tab_id;
+ int tree_id;
+
+ gfx::Vector2d location_offset;
+ ui::AXTree tree;
+};
+
// The native component of custom bindings for the chrome.automationInternal
// API.
-class AutomationInternalCustomBindings : public ObjectBackedNativeHandler {
+class AutomationInternalCustomBindings : public ObjectBackedNativeHandler,
+ public ui::AXTreeDelegate {
public:
explicit AutomationInternalCustomBindings(ScriptContext* context);
~AutomationInternalCustomBindings() override;
- bool OnMessageReceived(const IPC::Message& message);
+ void OnMessageReceived(const IPC::Message& message);
private:
// Returns whether this extension has the "interact" permission set (either
@@ -39,10 +52,120 @@ class AutomationInternalCustomBindings : public ObjectBackedNativeHandler {
// Get the routing ID for the extension.
void GetRoutingID(const v8::FunctionCallbackInfo<v8::Value>& args);
+ // This is called by automation_internal_custom_bindings.js to indicate
+ // that an API was called that needs access to accessibility trees. This
+ // enables the MessageFilter that allows us to listen to accessibility
+ // events forwarded to this process.
+ void StartCachingAccessibilityTrees(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Called when an accessibility tree is destroyed and needs to be
+ // removed from our cache.
+ // Args: int ax_tree_id
+ void DestroyAccessibilityTree(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ //
+ // Access the cached accessibility trees and properties of their nodes.
+ //
+
+ // Args: int ax_tree_id, Returns: int root_node_id.
+ void GetRootID(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, Returns: int parent_node_id.
+ void GetParentID(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, Returns: int child_count.
+ void GetChildCount(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, Returns: int child_id.
+ void GetChildIDAtIndex(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, Returns: int index_in_parent.
+ void GetIndexInParent(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id
+ // Returns: JS object with a string key for each state flag that's set.
+ void GetState(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, Returns: string role_name
+ void GetRole(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id
+ // Returns: JS object with {left, top, width, height}
+ void GetLocation(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, string attribute_name
+ // Returns: string attribute_value.
+ void GetStringAttribute(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, string attribute_name
+ // Returns: bool attribute_value.
+ void GetBoolAttribute(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, string attribute_name
+ // Returns: int attribute_value.
+ void GetIntAttribute(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, string attribute_name
+ // Returns: float attribute_value.
+ void GetFloatAttribute(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, string attribute_name
+ // Returns: JS array of int attribute_values.
+ void GetIntListAttribute(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Args: int ax_tree_id, int node_id, string attribute_name
+ // Returns: string attribute_value.
+ void GetHtmlAttribute(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ //
+ // Helper functions.
+ //
+
+ // Throw an exception if we get bad arguments.
+ void ThrowInvalidArgumentsException(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // For any function that takes int ax_tree_id, int node_id as its first
+ // two arguments, returns the tree and node it corresponds to, or returns
+ // false if not found.
+ bool GetNodeHelper(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ TreeCache** out_cache,
+ ui::AXNode** out_node);
+
+ // For any function that takes int ax_tree_id, int node_id, string attr
+ // as its first, returns the node it corresponds to and the string as
+ // a UTF8 string.
+ bool GetAttributeHelper(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ ui::AXNode** out_node,
+ std::string* out_attribute_name);
+
+ // Create a V8 string from a string.
+ v8::Local<v8::Value> CreateV8String(const char* str);
+ v8::Local<v8::Value> CreateV8String(const std::string& str);
+
// Handle accessibility events from the browser process.
void OnAccessibilityEvent(
const ExtensionMsg_AccessibilityEventParams& params);
+ // AXTreeDelegate implementation.
+ void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
+ void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
+ void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override;
+ void OnNodeChanged(ui::AXTree* tree, ui::AXNode* node) override;
+ void OnAtomicUpdateFinished(ui::AXTree* tree,
+ bool root_changed,
+ const std::vector<Change>& changes) override;
+
+ void SendTreeChangeEvent(api::automation::TreeChangeType change_type,
+ ui::AXTree* tree,
+ ui::AXNode* node);
+
+ base::hash_map<int, TreeCache*> tree_id_to_tree_cache_map_;
+ base::hash_map<ui::AXTree*, TreeCache*> axtree_to_tree_cache_map_;
scoped_refptr<AutomationMessageFilter> message_filter_;
DISALLOW_COPY_AND_ASSIGN(AutomationInternalCustomBindings);
diff --git a/chrome/renderer/resources/extensions/automation/automation_node.js b/chrome/renderer/resources/extensions/automation/automation_node.js
index 5dc6b9b..e5de712 100644
--- a/chrome/renderer/resources/extensions/automation/automation_node.js
+++ b/chrome/renderer/resources/extensions/automation/automation_node.js
@@ -8,6 +8,124 @@ var automationInternal =
var IsInteractPermitted =
requireNative('automationInternal').IsInteractPermitted;
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @return {?number} The id of the root node.
+ */
+var GetRootID = requireNative('automationInternal').GetRootID;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?number} The id of the node's parent, or undefined if it's the
+ * root of its tree or if the tree or node wasn't found.
+ */
+var GetParentID = requireNative('automationInternal').GetParentID;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?number} The number of children of the node, or undefined if
+ * the tree or node wasn't found.
+ */
+var GetChildCount = requireNative('automationInternal').GetChildCount;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {number} childIndex An index of a child of this node.
+ * @return {?number} The id of the child at the given index, or undefined
+ * if the tree or node or child at that index wasn't found.
+ */
+var GetChildIDAtIndex = requireNative('automationInternal').GetChildIDAtIndex;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?number} The index of this node in its parent, or undefined if
+ * the tree or node or node parent wasn't found.
+ */
+var GetIndexInParent = requireNative('automationInternal').GetIndexInParent;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?Object} An object with a string key for every state flag set,
+ * or undefined if the tree or node or node parent wasn't found.
+ */
+var GetState = requireNative('automationInternal').GetState;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {string} The role of the node, or undefined if the tree or
+ * node wasn't found.
+ */
+var GetRole = requireNative('automationInternal').GetRole;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?automation.Rect} The location of the node, or undefined if
+ * the tree or node wasn't found.
+ */
+var GetLocation = requireNative('automationInternal').GetLocation;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of a string attribute.
+ * @return {?string} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetStringAttribute = requireNative('automationInternal').GetStringAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?boolean} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetBoolAttribute = requireNative('automationInternal').GetBoolAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?number} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetIntAttribute = requireNative('automationInternal').GetIntAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?number} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetFloatAttribute = requireNative('automationInternal').GetFloatAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?Array.<number>} The value of this attribute, or undefined
+ * if the tree, node, or attribute wasn't found.
+ */
+var GetIntListAttribute =
+ requireNative('automationInternal').GetIntListAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an HTML attribute.
+ * @return {?string} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetHtmlAttribute = requireNative('automationInternal').GetHtmlAttribute;
+
var lastError = require('lastError');
var logging = requireNative('logging');
var schema = requireNative('automationInternal').GetSchemaAdditions();
@@ -20,16 +138,12 @@ var utils = require('utils');
*/
function AutomationNodeImpl(root) {
this.rootImpl = root;
- this.childIds = [];
// Public attributes. No actual data gets set on this object.
- this.attributes = {};
- // Internal object holding all attributes.
- this.attributesInternal = {};
this.listeners = {};
- this.location = { left: 0, top: 0, width: 0, height: 0 };
}
AutomationNodeImpl.prototype = {
+ treeID: -1,
id: -1,
role: '',
state: { busy: true },
@@ -40,16 +154,51 @@ AutomationNodeImpl.prototype = {
},
get parent() {
- return this.hostTree || this.rootImpl.get(this.parentID);
+ if (this.hostNode_)
+ return this.hostNode_;
+ var parentID = GetParentID(this.treeID, this.id);
+ return this.rootImpl.get(parentID);
+ },
+
+ get state() {
+ return GetState(this.treeID, this.id);
+ },
+
+ get role() {
+ return GetRole(this.treeID, this.id);
+ },
+
+ get location() {
+ return GetLocation(this.treeID, this.id);
+ },
+
+ get indexInParent() {
+ return GetIndexInParent(this.treeID, this.id);
+ },
+
+ get childTree() {
+ var childTreeID = GetIntAttribute(this.treeID, this.id, 'childTreeId');
+ if (childTreeID)
+ return AutomationRootNodeImpl.get(childTreeID);
},
get firstChild() {
- return this.childTree || this.rootImpl.get(this.childIds[0]);
+ if (this.childTree)
+ return this.childTree;
+ if (!GetChildCount(this.treeID, this.id))
+ return undefined;
+ var firstChildID = GetChildIDAtIndex(this.treeID, this.id, 0);
+ return this.rootImpl.get(firstChildID);
},
get lastChild() {
- var childIds = this.childIds;
- return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]);
+ if (this.childTree)
+ return this.childTree;
+ var count = GetChildCount(this.treeID, this.id);
+ if (!count)
+ return undefined;
+ var lastChildID = GetChildIDAtIndex(this.treeID, this.id, count - 1);
+ return this.rootImpl.get(lastChildID);
},
get children() {
@@ -57,24 +206,28 @@ AutomationNodeImpl.prototype = {
return [this.childTree];
var children = [];
- for (var i = 0, childID; childID = this.childIds[i]; i++) {
- logging.CHECK(this.rootImpl.get(childID));
- children.push(this.rootImpl.get(childID));
+ var count = GetChildCount(this.treeID, this.id);
+ for (var i = 0; i < count; ++i) {
+ var childID = GetChildIDAtIndex(this.treeID, this.id, i);
+ var child = this.rootImpl.get(childID);
+ children.push(child);
}
return children;
},
get previousSibling() {
var parent = this.parent;
- if (parent && this.indexInParent > 0)
- return parent.children[this.indexInParent - 1];
+ var indexInParent = GetIndexInParent(this.treeID, this.id);
+ if (parent && indexInParent > 0)
+ return parent.children[indexInParent - 1];
return undefined;
},
get nextSibling() {
var parent = this.parent;
- if (parent && this.indexInParent < parent.children.length)
- return parent.children[this.indexInParent + 1];
+ var indexInParent = GetIndexInParent(this.treeID, this.id);
+ if (parent && indexInParent < parent.children.length)
+ return parent.children[indexInParent + 1];
return undefined;
},
@@ -170,12 +323,20 @@ AutomationNodeImpl.prototype = {
var impl = privates(this).impl;
if (!impl)
impl = this;
+
+ var parentID = GetParentID(this.treeID, this.id);
+ var count = GetChildCount(this.treeID, this.id);
+ var childIDs = [];
+ for (var i = 0; i < count; ++i) {
+ var childID = GetChildIDAtIndex(this.treeID, this.id, i);
+ childIDs.push(childID);
+ }
+
return 'node id=' + impl.id +
' role=' + this.role +
' state=' + $JSON.stringify(this.state) +
- ' parentID=' + impl.parentID +
- ' childIds=' + $JSON.stringify(impl.childIds) +
- ' attributes=' + $JSON.stringify(this.attributes);
+ ' parentID=' + parentID +
+ ' childIds=' + $JSON.stringify(childIDs);
},
dispatchEventAtCapturing_: function(event, path) {
@@ -219,9 +380,9 @@ AutomationNodeImpl.prototype = {
try {
listeners[i].callback(event);
} catch (e) {
- console.error('Error in event handler for ' + event.type +
- 'during phase ' + eventPhase + ': ' +
- e.message + '\nStack trace: ' + e.stack);
+ logging.WARNING('Error in event handler for ' + event.type +
+ ' during phase ' + eventPhase + ': ' +
+ e.message + '\nStack trace: ' + e.stack);
}
}
},
@@ -312,17 +473,14 @@ AutomationNodeImpl.prototype = {
}
if ('attributes' in params) {
for (var attribute in params.attributes) {
- if (!(attribute in this.attributesInternal))
- return false;
-
var attrValue = params.attributes[attribute];
if (typeof attrValue != 'object') {
- if (this.attributesInternal[attribute] !== attrValue)
+ if (this[attribute] !== attrValue)
return false;
} else if (attrValue instanceof RegExp) {
- if (typeof this.attributesInternal[attribute] != 'string')
+ if (typeof this[attribute] != 'string')
return false;
- if (!attrValue.test(this.attributesInternal[attribute]))
+ if (!attrValue.test(this[attribute]))
return false;
} else {
// TODO(aboxhall): handle intlist case.
@@ -334,171 +492,196 @@ AutomationNodeImpl.prototype = {
}
};
-// Maps an attribute to its default value in an invalidated node.
-// These attributes are taken directly from the Automation idl.
-var AutomationAttributeDefaults = {
- 'id': -1,
- 'role': '',
- 'state': {},
- 'location': { left: 0, top: 0, width: 0, height: 0 }
-};
-
-
-var AutomationAttributeTypes = [
- 'boolAttributes',
- 'floatAttributes',
- 'htmlAttributes',
- 'intAttributes',
- 'intlistAttributes',
- 'stringAttributes'
-];
-
-/**
- * Maps an attribute name to another attribute who's value is an id or an array
- * of ids referencing an AutomationNode.
- * @param {!Object<string>}
- * @const
- */
-var ATTRIBUTE_NAME_TO_ID_ATTRIBUTE = {
- 'aria-activedescendant': 'activedescendantId',
- 'aria-controls': 'controlsIds',
- 'aria-describedby': 'describedbyIds',
- 'aria-flowto': 'flowtoIds',
- 'aria-labelledby': 'labelledbyIds',
- 'aria-owns': 'ownsIds'
-};
-
-/**
- * A set of attributes ignored in the automation API.
- * @param {!Object<boolean>}
- * @const
- */
-var ATTRIBUTE_BLACKLIST = {'activedescendantId': true,
- 'childTreeId': true,
- 'controlsIds': true,
- 'describedbyIds': true,
- 'flowtoIds': true,
- 'labelledbyIds': true,
- 'ownsIds': true
-};
-
-function defaultStringAttribute(opt_defaultVal) {
- return { default: undefined, reflectFrom: 'stringAttributes' };
-}
-
-function defaultIntAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0;
- return { default: defaultVal, reflectFrom: 'intAttributes' };
-}
-
-function defaultFloatAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0;
- return { default: defaultVal, reflectFrom: 'floatAttributes' };
-}
-
-function defaultBoolAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : false;
- return { default: defaultVal, reflectFrom: 'boolAttributes' };
-}
-
-function defaultHtmlAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : '';
- return { default: defaultVal, reflectFrom: 'htmlAttributes' };
-}
-
-function defaultIntListAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : [];
- return { default: defaultVal, reflectFrom: 'intlistAttributes' };
-}
-
-function defaultNodeRefAttribute(idAttribute, opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : null;
- return { default: defaultVal,
- idFrom: 'intAttributes',
- idAttribute: idAttribute,
- isRef: true };
-}
-
-function defaultNodeRefListAttribute(idAttribute, opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : [];
- return { default: [],
- idFrom: 'intlistAttributes',
- idAttribute: idAttribute,
- isRef: true };
-}
-
-// Maps an attribute to its default value in an invalidated node.
-// These attributes are taken directly from the Automation idl.
-var DefaultMixinAttributes = {
- description: defaultStringAttribute(),
- help: defaultStringAttribute(),
- name: defaultStringAttribute(),
- value: defaultStringAttribute(),
- htmlTag: defaultStringAttribute(),
- hierarchicalLevel: defaultIntAttribute(),
- controls: defaultNodeRefListAttribute('controlsIds'),
- describedby: defaultNodeRefListAttribute('describedbyIds'),
- flowto: defaultNodeRefListAttribute('flowtoIds'),
- labelledby: defaultNodeRefListAttribute('labelledbyIds'),
- owns: defaultNodeRefListAttribute('ownsIds'),
- wordStarts: defaultIntListAttribute(),
- wordEnds: defaultIntListAttribute()
-};
-
-var ActiveDescendantMixinAttribute = {
- activedescendant: defaultNodeRefAttribute('activedescendantId')
-};
-
-var LinkMixinAttributes = {
- url: defaultStringAttribute()
-};
-
-var DocumentMixinAttributes = {
- docUrl: defaultStringAttribute(),
- docTitle: defaultStringAttribute(),
- docLoaded: defaultStringAttribute(),
- docLoadingProgress: defaultFloatAttribute()
-};
-
-var ScrollableMixinAttributes = {
- scrollX: defaultIntAttribute(),
- scrollXMin: defaultIntAttribute(),
- scrollXMax: defaultIntAttribute(),
- scrollY: defaultIntAttribute(),
- scrollYMin: defaultIntAttribute(),
- scrollYMax: defaultIntAttribute()
-};
-
-var EditableTextMixinAttributes = {
- textSelStart: defaultIntAttribute(-1),
- textSelEnd: defaultIntAttribute(-1),
- type: defaultHtmlAttribute()
-};
-
-var RangeMixinAttributes = {
- valueForRange: defaultFloatAttribute(),
- minValueForRange: defaultFloatAttribute(),
- maxValueForRange: defaultFloatAttribute()
-};
-
-var TableMixinAttributes = {
- tableRowCount: defaultIntAttribute(),
- tableColumnCount: defaultIntAttribute()
-};
+var stringAttributes = [
+ 'accessKey',
+ 'action',
+ 'ariaInvalidValue',
+ 'autoComplete',
+ 'containerLiveRelevant',
+ 'containerLiveStatus',
+ 'description',
+ 'display',
+ 'docDoctype',
+ 'docMimetype',
+ 'docTitle',
+ 'docUrl',
+ 'dropeffect',
+ 'help',
+ 'htmlTag',
+ 'liveRelevant',
+ 'liveStatus',
+ 'name',
+ 'placeholder',
+ 'shortcut',
+ 'textInputType',
+ 'url',
+ 'value'];
+
+var boolAttributes = [
+ 'ariaReadonly',
+ 'buttonMixed',
+ 'canSetValue',
+ 'canvasHasFallback',
+ 'containerLiveAtomic',
+ 'containerLiveBusy',
+ 'docLoaded',
+ 'grabbed',
+ 'isAxTreeHost',
+ 'liveAtomic',
+ 'liveBusy',
+ 'updateLocationOnly'];
+
+var intAttributes = [
+ 'backgroundColor',
+ 'color',
+ 'colorValue',
+ 'hierarchicalLevel',
+ 'invalidState',
+ 'posInSet',
+ 'scrollX',
+ 'scrollXMax',
+ 'scrollXMin',
+ 'scrollY',
+ 'scrollYMax',
+ 'scrollYMin',
+ 'setSize',
+ 'sortDirection',
+ 'tableCellColumnIndex',
+ 'tableCellColumnSpan',
+ 'tableCellRowIndex',
+ 'tableCellRowSpan',
+ 'tableColumnCount',
+ 'tableColumnIndex',
+ 'tableRowCount',
+ 'tableRowIndex',
+ 'textDirection',
+ 'textSelEnd',
+ 'textSelStart',
+ 'textStyle'];
+
+var nodeRefAttributes = [
+ ['activedescendantId', 'activedescendant'],
+ ['tableColumnHeaderId', 'tableColumnHeader'],
+ ['tableHeaderId', 'tableHeader'],
+ ['tableRowHeaderId', 'tableRowHeader'],
+ ['titleUiElement', 'titleUIElement']];
+
+var intListAttributes = [
+ 'characterOffsets',
+ 'lineBreaks',
+ 'wordEnds',
+ 'wordStarts'];
+
+var nodeRefListAttributes = [
+ ['cellIds', 'cells'],
+ ['controlsIds', 'controls'],
+ ['describedbyIds', 'describedBy'],
+ ['flowtoIds', 'flowTo'],
+ ['labelledbyIds', 'labelledBy'],
+ ['uniqueCellIds', 'uniqueCells']];
+
+var floatAttributes = [
+ 'docLoadingProgress',
+ 'valueForRange',
+ 'minValueForRange',
+ 'maxValueForRange',
+ 'fontSize'];
+
+var htmlAttributes = [
+ ['type', 'inputType']];
+
+var publicAttributes = [];
+
+stringAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetStringAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+boolAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetBoolAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+intAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetIntAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+nodeRefAttributes.forEach(function (params) {
+ var srcAttributeName = params[0];
+ var dstAttributeName = params[1];
+ publicAttributes.push(dstAttributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
+ get: function() {
+ var id = GetIntAttribute(this.treeID, this.id, srcAttributeName);
+ if (id)
+ return this.rootImpl.get(id);
+ else
+ return undefined;
+ }
+ });
+});
+
+intListAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetIntListAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+nodeRefListAttributes.forEach(function (params) {
+ var srcAttributeName = params[0];
+ var dstAttributeName = params[1];
+ publicAttributes.push(dstAttributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
+ get: function() {
+ var ids = GetIntListAttribute(this.treeID, this.id, srcAttributeName);
+ if (!ids)
+ return undefined;
+ var result = [];
+ for (var i = 0; i < ids.length; ++i) {
+ var node = this.rootImpl.get(ids[i]);
+ if (node)
+ result.push(node);
+ }
+ return result;
+ }
+ });
+});
-var TableCellMixinAttributes = {
- tableCellColumnIndex: defaultIntAttribute(),
- tableCellColumnSpan: defaultIntAttribute(1),
- tableCellRowIndex: defaultIntAttribute(),
- tableCellRowSpan: defaultIntAttribute(1)
-};
+floatAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetFloatAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
-var LiveRegionMixinAttributes = {
- containerLiveAtomic: defaultBoolAttribute(),
- containerLiveBusy: defaultBoolAttribute(),
- containerLiveRelevant: defaultStringAttribute(),
- containerLiveStatus: defaultStringAttribute(),
-};
+htmlAttributes.forEach(function (params) {
+ var srcAttributeName = params[0];
+ var dstAttributeName = params[1];
+ publicAttributes.push(dstAttributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
+ get: function() {
+ return GetHtmlAttribute(this.treeID, this.id, srcAttributeName);
+ }
+ });
+});
/**
* AutomationRootNode.
@@ -523,107 +706,88 @@ function AutomationRootNodeImpl(treeID) {
this.axNodeDataCache_ = {};
}
+AutomationRootNodeImpl.idToAutomationRootNode_ = {};
+
+AutomationRootNodeImpl.get = function(treeID) {
+ var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
+ return result || undefined;
+}
+
+AutomationRootNodeImpl.getOrCreate = function(treeID) {
+ if (AutomationRootNodeImpl.idToAutomationRootNode_[treeID])
+ return AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
+ var result = new AutomationRootNode(treeID);
+ AutomationRootNodeImpl.idToAutomationRootNode_[treeID] = result;
+ return result;
+}
+
+AutomationRootNodeImpl.destroy = function(treeID) {
+ delete AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
+}
+
AutomationRootNodeImpl.prototype = {
__proto__: AutomationNodeImpl.prototype,
+ /**
+ * @type {boolean}
+ */
isRootNode: true,
+
+ /**
+ * @type {number}
+ */
treeID: -1,
+ /**
+ * The parent of this node from a different tree.
+ * @type {?AutomationNode}
+ * @private
+ */
+ hostNode_: null,
+
+ /**
+ * A map from id to AutomationNode.
+ * @type {Object.<number, AutomationNode>}
+ * @private
+ */
+ axNodeDataCache_: null,
+
+ get id() {
+ return GetRootID(this.treeID);
+ },
+
get: function(id) {
if (id == undefined)
return undefined;
- return this.axNodeDataCache_[id];
- },
+ if (id == this.id)
+ return this.wrapper;
- unserialize: function(update) {
- var updateState = { pendingNodes: {}, newNodes: {} };
- var oldRootId = this.id;
+ var obj = this.axNodeDataCache_[id];
+ if (obj)
+ return obj;
- if (update.nodeIdToClear < 0) {
- logging.WARNING('Bad nodeIdToClear: ' + update.nodeIdToClear);
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- } else if (update.nodeIdToClear > 0) {
- var nodeToClear = this.axNodeDataCache_[update.nodeIdToClear];
- if (!nodeToClear) {
- logging.WARNING('Bad nodeIdToClear: ' + update.nodeIdToClear +
- ' (not in cache)');
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
- if (nodeToClear === this.wrapper) {
- this.invalidate_(nodeToClear);
- } else {
- var children = nodeToClear.children;
- for (var i = 0; i < children.length; i++)
- this.invalidate_(children[i]);
- var nodeToClearImpl = privates(nodeToClear).impl;
- nodeToClearImpl.childIds = []
- updateState.pendingNodes[nodeToClearImpl.id] = nodeToClear;
- }
- }
-
- for (var i = 0; i < update.nodes.length; i++) {
- if (!this.updateNode_(update.nodes[i], updateState))
- return false;
- }
-
- if (Object.keys(updateState.pendingNodes).length > 0) {
- logging.WARNING('Nodes left pending by the update: ' +
- $JSON.stringify(updateState.pendingNodes));
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
+ obj = new AutomationNode(this);
+ privates(obj).impl.treeID = this.treeID;
+ privates(obj).impl.id = id;
+ this.axNodeDataCache_[id] = obj;
- // Notify tree change observers of new nodes.
- // TODO(dmazzoni): Notify tree change observers of changed nodes,
- // and handle subtreeCreated and nodeCreated properly.
- var observers = automationUtil.treeChangeObservers;
- if (observers.length > 0) {
- for (var nodeId in updateState.newNodes) {
- var node = updateState.newNodes[nodeId];
- var treeChange =
- {target: node, type: schema.TreeChangeType.nodeCreated};
- for (var i = 0; i < observers.length; i++) {
- try {
- observers[i](treeChange);
- } catch (e) {
- console.error('Error in tree change observer for ' +
- treeChange.type + ': ' + e.message +
- '\nStack trace: ' + e.stack);
- }
- }
- }
- }
+ return obj;
+ },
- return true;
+ remove: function(id) {
+ delete this.axNodeDataCache_[id];
},
destroy: function() {
- if (this.hostTree)
- this.hostTree.childTree = undefined;
- this.hostTree = undefined;
-
this.dispatchEvent(schema.EventType.destroyed);
- this.invalidate_(this.wrapper);
},
- onAccessibilityEvent: function(eventParams) {
- if (!this.unserialize(eventParams.update)) {
- logging.WARNING('unserialization failed');
- return false;
- }
+ setHostNode(hostNode) {
+ this.hostNode_ = hostNode;
+ },
+ onAccessibilityEvent: function(eventParams) {
var targetNode = this.get(eventParams.targetID);
if (targetNode) {
var targetNodeImpl = privates(targetNode).impl;
@@ -651,374 +815,8 @@ AutomationRootNodeImpl.prototype = {
}
return toStringInternal(this, 0);
},
-
- invalidate_: function(node) {
- if (!node)
- return;
-
- // Notify tree change observers of the removed node.
- var observers = automationUtil.treeChangeObservers;
- if (observers.length > 0) {
- var treeChange = {target: node, type: schema.TreeChangeType.nodeRemoved};
- for (var i = 0; i < observers.length; i++) {
- try {
- observers[i](treeChange);
- } catch (e) {
- console.error('Error in tree change observer for ' + treeChange.type +
- ': ' + e.message + '\nStack trace: ' + e.stack);
- }
- }
- }
-
- var children = node.children;
-
- for (var i = 0, child; child = children[i]; i++) {
- // Do not invalidate into subrooted nodes.
- // TODO(dtseng): Revisit logic once out of proc iframes land.
- if (child.root != node.root)
- continue;
- this.invalidate_(child);
- }
-
- // Retrieve the internal AutomationNodeImpl instance for this node.
- // This object is not accessible outside of bindings code, but we can access
- // it here.
- var nodeImpl = privates(node).impl;
- var id = nodeImpl.id;
- for (var key in AutomationAttributeDefaults) {
- nodeImpl[key] = AutomationAttributeDefaults[key];
- }
-
- nodeImpl.attributesInternal = {};
- for (var key in DefaultMixinAttributes) {
- var mixinAttribute = DefaultMixinAttributes[key];
- if (!mixinAttribute.isRef)
- nodeImpl.attributesInternal[key] = mixinAttribute.default;
- }
- nodeImpl.childIds = [];
- nodeImpl.id = id;
- delete this.axNodeDataCache_[id];
- },
-
- deleteOldChildren_: function(node, newChildIds) {
- // Create a set of child ids in |src| for fast lookup, and return false
- // if a duplicate is found;
- var newChildIdSet = {};
- for (var i = 0; i < newChildIds.length; i++) {
- var childId = newChildIds[i];
- if (newChildIdSet[childId]) {
- logging.WARNING('Node ' + privates(node).impl.id +
- ' has duplicate child id ' + childId);
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
- newChildIdSet[newChildIds[i]] = true;
- }
-
- // Delete the old children.
- var nodeImpl = privates(node).impl;
- var oldChildIds = nodeImpl.childIds;
- for (var i = 0; i < oldChildIds.length;) {
- var oldId = oldChildIds[i];
- if (!newChildIdSet[oldId]) {
- this.invalidate_(this.axNodeDataCache_[oldId]);
- oldChildIds.splice(i, 1);
- } else {
- i++;
- }
- }
- nodeImpl.childIds = oldChildIds;
-
- return true;
- },
-
- createNewChildren_: function(node, newChildIds, updateState) {
- logging.CHECK(node);
- var success = true;
-
- for (var i = 0; i < newChildIds.length; i++) {
- var childId = newChildIds[i];
- var childNode = this.axNodeDataCache_[childId];
- if (childNode) {
- if (childNode.parent != node) {
- var parentId = -1;
- if (childNode.parent) {
- var parentImpl = privates(childNode.parent).impl;
- parentId = parentImpl.id;
- }
- // This is a serious error - nodes should never be reparented.
- // If this case occurs, continue so this node isn't left in an
- // inconsistent state, but return failure at the end.
- logging.WARNING('Node ' + childId + ' reparented from ' +
- parentId + ' to ' + privates(node).impl.id);
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- success = false;
- continue;
- }
- } else {
- childNode = new AutomationNode(this);
- this.axNodeDataCache_[childId] = childNode;
- privates(childNode).impl.id = childId;
- updateState.pendingNodes[childId] = childNode;
- updateState.newNodes[childId] = childNode;
- }
- privates(childNode).impl.indexInParent = i;
- privates(childNode).impl.parentID = privates(node).impl.id;
- }
-
- return success;
- },
-
- setData_: function(node, nodeData) {
- var nodeImpl = privates(node).impl;
-
- // TODO(dtseng): Make into set listing all hosting node roles.
- if (nodeData.role == schema.RoleType.webView ||
- nodeData.role == schema.RoleType.embeddedObject) {
- if (nodeImpl.childTreeID !== nodeData.intAttributes.childTreeId)
- nodeImpl.pendingChildFrame = true;
-
- if (nodeImpl.pendingChildFrame) {
- nodeImpl.childTreeID = nodeData.intAttributes.childTreeId;
- automationUtil.storeTreeCallback(nodeImpl.childTreeID, function(root) {
- nodeImpl.pendingChildFrame = false;
- nodeImpl.childTree = root;
- privates(root).impl.hostTree = node;
- if (root.attributes.docLoadingProgress == 1)
- privates(root).impl.dispatchEvent(schema.EventType.loadComplete);
- nodeImpl.dispatchEvent(schema.EventType.childrenChanged);
- });
- automationInternal.enableFrame(nodeImpl.childTreeID);
- }
- }
- for (var key in AutomationAttributeDefaults) {
- if (key in nodeData)
- nodeImpl[key] = nodeData[key];
- else
- nodeImpl[key] = AutomationAttributeDefaults[key];
- }
-
- // Set all basic attributes.
- this.mixinAttributes_(nodeImpl, DefaultMixinAttributes, nodeData);
-
- // If this is a rootWebArea or webArea, set document attributes.
- if (nodeData.role == schema.RoleType.rootWebArea ||
- nodeData.role == schema.RoleType.webArea) {
- this.mixinAttributes_(nodeImpl, DocumentMixinAttributes, nodeData);
- }
-
- // If this is a scrollable area, set scrollable attributes.
- for (var scrollAttr in ScrollableMixinAttributes) {
- var spec = ScrollableMixinAttributes[scrollAttr];
- if (this.findAttribute_(scrollAttr, spec, nodeData) !== undefined) {
- this.mixinAttributes_(nodeImpl, ScrollableMixinAttributes, nodeData);
- break;
- }
- }
-
- // If this is inside a live region, set live region mixins.
- var attr = 'containerLiveStatus';
- var spec = LiveRegionMixinAttributes[attr];
- if (this.findAttribute_(attr, spec, nodeData) !== undefined) {
- this.mixinAttributes_(nodeImpl, LiveRegionMixinAttributes, nodeData);
- }
-
- // If this is a link, set link attributes
- if (nodeData.role == 'link') {
- this.mixinAttributes_(nodeImpl, LinkMixinAttributes, nodeData);
- }
-
- // If this is an editable text area, set editable text attributes.
- if (nodeData.role == schema.RoleType.textField ||
- nodeData.role == schema.RoleType.spinButton) {
- this.mixinAttributes_(nodeImpl, EditableTextMixinAttributes, nodeData);
- }
-
- // If this is a range type, set range attributes.
- if (nodeData.role == schema.RoleType.progressIndicator ||
- nodeData.role == schema.RoleType.scrollBar ||
- nodeData.role == schema.RoleType.slider ||
- nodeData.role == schema.RoleType.spinButton) {
- this.mixinAttributes_(nodeImpl, RangeMixinAttributes, nodeData);
- }
-
- // If this is a table, set table attributes.
- if (nodeData.role == schema.RoleType.table) {
- this.mixinAttributes_(nodeImpl, TableMixinAttributes, nodeData);
- }
-
- // If this is a table cell, set table cell attributes.
- if (nodeData.role == schema.RoleType.cell) {
- this.mixinAttributes_(nodeImpl, TableCellMixinAttributes, nodeData);
- }
-
- // If this has an active descendant, expose it.
- if ('intAttributes' in nodeData &&
- 'activedescendantId' in nodeData.intAttributes) {
- this.mixinAttributes_(nodeImpl, ActiveDescendantMixinAttribute, nodeData);
- }
-
- for (var i = 0; i < AutomationAttributeTypes.length; i++) {
- var attributeType = AutomationAttributeTypes[i];
- for (var attributeName in nodeData[attributeType]) {
- nodeImpl.attributesInternal[attributeName] =
- nodeData[attributeType][attributeName];
- if (ATTRIBUTE_BLACKLIST.hasOwnProperty(attributeName) ||
- nodeImpl.attributes.hasOwnProperty(attributeName)) {
- continue;
- } else if (
- ATTRIBUTE_NAME_TO_ID_ATTRIBUTE.hasOwnProperty(attributeName)) {
- this.defineReadonlyAttribute_(nodeImpl,
- nodeImpl.attributes,
- attributeName,
- true);
- } else {
- this.defineReadonlyAttribute_(nodeImpl,
- nodeImpl.attributes,
- attributeName);
- }
- }
- }
- },
-
- mixinAttributes_: function(nodeImpl, attributes, nodeData) {
- for (var attribute in attributes) {
- var spec = attributes[attribute];
- if (spec.isRef)
- this.mixinRelationshipAttribute_(nodeImpl, attribute, spec, nodeData);
- else
- this.mixinAttribute_(nodeImpl, attribute, spec, nodeData);
- }
- },
-
- mixinAttribute_: function(nodeImpl, attribute, spec, nodeData) {
- var value = this.findAttribute_(attribute, spec, nodeData);
- if (value === undefined)
- value = spec.default;
- nodeImpl.attributesInternal[attribute] = value;
- this.defineReadonlyAttribute_(nodeImpl, nodeImpl, attribute);
- },
-
- mixinRelationshipAttribute_: function(nodeImpl, attribute, spec, nodeData) {
- var idAttribute = spec.idAttribute;
- var idValue = spec.default;
- if (spec.idFrom in nodeData) {
- idValue = idAttribute in nodeData[spec.idFrom]
- ? nodeData[spec.idFrom][idAttribute] : idValue;
- }
-
- // Ok to define a list attribute with an empty list, but not a
- // single ref with a null ID.
- if (idValue === null)
- return;
-
- nodeImpl.attributesInternal[idAttribute] = idValue;
- this.defineReadonlyAttribute_(
- nodeImpl, nodeImpl, attribute, true, idAttribute);
- },
-
- findAttribute_: function(attribute, spec, nodeData) {
- if (!('reflectFrom' in spec))
- return;
- var attributeGroup = spec.reflectFrom;
- if (!(attributeGroup in nodeData))
- return;
-
- return nodeData[attributeGroup][attribute];
- },
-
- defineReadonlyAttribute_: function(
- node, object, attributeName, opt_isIDRef, opt_idAttribute) {
- if (attributeName in object)
- return;
-
- if (opt_isIDRef) {
- $Object.defineProperty(object, attributeName, {
- enumerable: true,
- get: function() {
- var idAttribute = opt_idAttribute ||
- ATTRIBUTE_NAME_TO_ID_ATTRIBUTE[attributeName];
- var idValue = node.attributesInternal[idAttribute];
- if (Array.isArray(idValue)) {
- return idValue.map(function(current) {
- return node.rootImpl.get(current);
- }, this);
- }
- return node.rootImpl.get(idValue);
- }.bind(this),
- });
- } else {
- $Object.defineProperty(object, attributeName, {
- enumerable: true,
- get: function() {
- return node.attributesInternal[attributeName];
- }.bind(this),
- });
- }
-
- if (object instanceof AutomationNodeImpl) {
- // Also expose attribute publicly on the wrapper.
- $Object.defineProperty(object.wrapper, attributeName, {
- enumerable: true,
- get: function() {
- return object[attributeName];
- },
- });
-
- }
- },
-
- updateNode_: function(nodeData, updateState) {
- var node = this.axNodeDataCache_[nodeData.id];
- var didUpdateRoot = false;
- if (node) {
- delete updateState.pendingNodes[privates(node).impl.id];
- } else {
- if (nodeData.role != schema.RoleType.rootWebArea &&
- nodeData.role != schema.RoleType.desktop) {
- logging.WARNING(String(nodeData.id) +
- ' is not in the cache and not the new root.');
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
- // |this| is an AutomationRootNodeImpl; retrieve the
- // AutomationRootNode instance instead.
- node = this.wrapper;
- didUpdateRoot = true;
- updateState.newNodes[this.id] = this.wrapper;
- }
- this.setData_(node, nodeData);
-
- // TODO(aboxhall): send onChanged event?
- logging.CHECK(node);
- if (!this.deleteOldChildren_(node, nodeData.childIds)) {
- if (didUpdateRoot) {
- this.invalidate_(this.wrapper);
- }
- return false;
- }
- var nodeImpl = privates(node).impl;
-
- var success = this.createNewChildren_(node,
- nodeData.childIds,
- updateState);
- nodeImpl.childIds = nodeData.childIds;
- this.axNodeDataCache_[nodeImpl.id] = node;
-
- return success;
- }
};
-
var AutomationNode = utils.expose('AutomationNode',
AutomationNodeImpl,
{ functions: ['doDefault',
@@ -1033,7 +831,8 @@ var AutomationNode = utils.expose('AutomationNode',
'removeEventListener',
'domQuerySelector',
'toString' ],
- readonly: ['parent',
+ readonly: publicAttributes.concat(
+ ['parent',
'firstChild',
'lastChild',
'children',
@@ -1043,13 +842,24 @@ var AutomationNode = utils.expose('AutomationNode',
'role',
'state',
'location',
- 'attributes',
'indexInParent',
- 'root'] });
+ 'root']) });
var AutomationRootNode = utils.expose('AutomationRootNode',
AutomationRootNodeImpl,
{ superclass: AutomationNode });
+AutomationRootNode.get = function(treeID) {
+ return AutomationRootNodeImpl.get(treeID);
+}
+
+AutomationRootNode.getOrCreate = function(treeID) {
+ return AutomationRootNodeImpl.getOrCreate(treeID);
+}
+
+AutomationRootNode.destroy = function(treeID) {
+ AutomationRootNodeImpl.destroy(treeID);
+}
+
exports.AutomationNode = AutomationNode;
exports.AutomationRootNode = AutomationRootNode;
diff --git a/chrome/renderer/resources/extensions/automation_custom_bindings.js b/chrome/renderer/resources/extensions/automation_custom_bindings.js
index b2fc5c3b7..59234f2 100644
--- a/chrome/renderer/resources/extensions/automation_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/automation_custom_bindings.js
@@ -16,6 +16,11 @@ var logging = requireNative('logging');
var nativeAutomationInternal = requireNative('automationInternal');
var GetRoutingID = nativeAutomationInternal.GetRoutingID;
var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions;
+var DestroyAccessibilityTree =
+ nativeAutomationInternal.DestroyAccessibilityTree;
+var GetIntAttribute = nativeAutomationInternal.GetIntAttribute;
+var StartCachingAccessibilityTrees =
+ nativeAutomationInternal.StartCachingAccessibilityTrees;
var schema = GetSchemaAdditions();
/**
@@ -24,7 +29,6 @@ var schema = GetSchemaAdditions();
window.automationUtil = function() {};
// TODO(aboxhall): Look into using WeakMap
-var idToAutomationRootNode = {};
var idToCallback = {};
var DESKTOP_TREE_ID = 0;
@@ -33,7 +37,7 @@ automationUtil.storeTreeCallback = function(id, callback) {
if (!callback)
return;
- var targetTree = idToAutomationRootNode[id];
+ var targetTree = AutomationRootNode.get(id);
if (!targetTree) {
// If we haven't cached the tree, hold the callback until the tree is
// populated by the initial onAccessibilityEvent call.
@@ -58,6 +62,7 @@ automation.registerCustomHook(function(bindingsAPI) {
// TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj.
apiFunctions.setHandleRequest('getTree', function getTree(tabID, callback) {
var routingID = GetRoutingID();
+ StartCachingAccessibilityTrees();
// enableTab() ensures the renderer for the active or specified tab has
// accessibility enabled, and fetches its ax tree id to use as
@@ -79,8 +84,8 @@ automation.registerCustomHook(function(bindingsAPI) {
var desktopTree = null;
apiFunctions.setHandleRequest('getDesktop', function(callback) {
- desktopTree =
- idToAutomationRootNode[DESKTOP_TREE_ID];
+ StartCachingAccessibilityTrees();
+ desktopTree = AutomationRootNode.get(DESKTOP_TREE_ID);
if (!desktopTree) {
if (DESKTOP_TREE_ID in idToCallback)
idToCallback[DESKTOP_TREE_ID].push(callback);
@@ -93,8 +98,7 @@ automation.registerCustomHook(function(bindingsAPI) {
// scope.
automationInternal.enableDesktop(routingID, function() {
if (lastError.hasError(chrome)) {
- delete idToAutomationRootNode[
- DESKTOP_TREE_ID];
+ AutomationRootNode.destroy(DESKTOP_TREE_ID);
callback();
return;
}
@@ -125,19 +129,65 @@ automation.registerCustomHook(function(bindingsAPI) {
});
+automationInternal.onTreeChange.addListener(function(treeID,
+ nodeID,
+ changeType) {
+ var tree = AutomationRootNode.get(treeID);
+ if (!tree)
+ return;
+
+ var node = privates(tree).impl.get(nodeID);
+ if (!node)
+ return;
+
+ if (node.role == 'webView' || node.role == 'embeddedObject') {
+ // A WebView in the desktop tree has a different AX tree as its child.
+ // When we encounter a WebView with a child AX tree id that we don't
+ // currently have cached, explicitly request that AX tree from the
+ // browser process and set up a callback when it loads to attach that
+ // tree as a child of this node and fire appropriate events.
+ var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId');
+ if (!AutomationRootNode.get(childTreeID)) {
+ automationUtil.storeTreeCallback(childTreeID, function(root) {
+ privates(root).impl.setHostNode(node);
+
+ if (root.docLoaded)
+ privates(root).impl.dispatchEvent(schema.EventType.loadComplete);
+
+ privates(node).impl.dispatchEvent(schema.EventType.childrenChanged);
+ });
+
+ automationInternal.enableFrame(childTreeID);
+ }
+ }
+
+ var treeChange = {target: node, type: changeType};
+
+ // Make a copy of the observers in case one of these callbacks tries
+ // to change the list of observers.
+ var observers = automationUtil.treeChangeObservers.slice();
+ for (var i = 0; i < observers.length; i++) {
+ try {
+ observers[i](treeChange);
+ } catch (e) {
+ logging.WARNING('Error in tree change observer for ' +
+ treeChange.type + ': ' + e.message +
+ '\nStack trace: ' + e.stack);
+ }
+ }
+
+ if (changeType == schema.TreeChangeType.nodeRemoved) {
+ privates(tree).impl.remove(nodeID);
+ }
+});
+
// Listen to the automationInternal.onAccessibilityEvent event, which is
// essentially a proxy for the AccessibilityHostMsg_Events IPC from the
// renderer.
automationInternal.onAccessibilityEvent.addListener(function(data) {
var id = data.treeID;
- var targetTree = idToAutomationRootNode[id];
- if (!targetTree) {
- // If this is the first time we've gotten data for this tree, it will
- // contain all of the tree's data, so create a new tree which will be
- // bootstrapped from |data|.
- targetTree = new AutomationRootNode(id);
- idToAutomationRootNode[id] = targetTree;
- }
+ var targetTree = AutomationRootNode.getOrCreate(id);
+
if (!privates(targetTree).impl.onAccessibilityEvent(data))
return;
@@ -149,7 +199,7 @@ automationInternal.onAccessibilityEvent.addListener(function(data) {
// attribute or child nodes. If we've got that, wait for the full tree before
// calling the callback.
// TODO(dmazzoni): Don't send down placeholder (crbug.com/397553)
- if (id != DESKTOP_TREE_ID && !targetTree.attributes.url &&
+ if (id != DESKTOP_TREE_ID && !targetTree.url &&
targetTree.children.length == 0) {
return;
}
@@ -158,7 +208,6 @@ automationInternal.onAccessibilityEvent.addListener(function(data) {
// have been cached in idToCallback, so call and delete it now that we
// have the complete tree.
for (var i = 0; i < idToCallback[id].length; i++) {
- console.log('calling getTree() callback');
var callback = idToCallback[id][i];
callback(targetTree);
}
@@ -166,14 +215,17 @@ automationInternal.onAccessibilityEvent.addListener(function(data) {
});
automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) {
- var targetTree = idToAutomationRootNode[id];
+ // Destroy the AutomationRootNode.
+ var targetTree = AutomationRootNode.get(id);
if (targetTree) {
privates(targetTree).impl.destroy();
- delete idToAutomationRootNode[id];
+ AutomationRootNode.destroy(id);
} else {
logging.WARNING('no targetTree to destroy');
}
- delete idToAutomationRootNode[id];
+
+ // Destroy the native cache of the accessibility tree.
+ DestroyAccessibilityTree(id);
});
exports.binding = automation.generate();
diff --git a/chrome/test/data/extensions/api_test/automation/sites/mixins.html b/chrome/test/data/extensions/api_test/automation/sites/attributes.html
index c27429d..5b8f63f 100644
--- a/chrome/test/data/extensions/api_test/automation/sites/mixins.html
+++ b/chrome/test/data/extensions/api_test/automation/sites/attributes.html
@@ -5,12 +5,12 @@
-->
<html>
<head>
-<title>Automation Tests - Mixin attributes</title>
+<title>Automation Tests - Attributes</title>
</head>
<body>
- <!-- activedescendant mixin, owns default mixin-->
+ <!-- activedescendant attribute, owns default attribute-->
<input type="text" aria-activedescendant="opt6" aria-readonly="true"
- aria-owns="combobox-list" aria-autocomplete="list" role="combobox"
+ aria-autocomplete="list" role="combobox"
id="combobox-edit">
<ul aria-expanded="true" role="listbox" id="combobox-list">
<li role="option" id="opt1">Alabama</li>
@@ -22,14 +22,14 @@
<li role="option" id="opt7">Colorado</li>
</ul>
- <!-- link mixins -->
+ <!-- link attributes -->
<a href="about://blank" id="real-link">Real link</a>
<div role="link" id="link-role">ARIA link</div>
- <!-- editable text mixins -->
- <input type="text" value="Text input" id="text-input"></input>
- <textarea id="textarea">Textarea</textarea>
- <div tabindex="0" contenteditable role="textbox" id="textbox-role">
+ <!-- editable text attributes -->
+ <input type="text" value="Text input" aria-label="text-input" id="text-input"></input>
+ <textarea aria-label="textarea">Textarea</textarea>
+ <div tabindex="0" contenteditable role="textbox" aria-label="textbox-role">
Textbox
</div>
@@ -37,21 +37,22 @@
document.querySelector('#text-input').setSelectionRange(2, 8);
</script>
- <!-- range mixins -->
- <input type="range" id="range-input" max="5" value="4"></input>
+ <!-- range attributes -->
+ <input type="range" aria-label="range-input" max="5" value="4"></input>
<div tabindex="0" role="slider" aria-valuemin="1" aria-valuemax="10"
- aria-valuenow="7" aria-valuetext="seven stars" id="slider-role"></div>
+ aria-valuenow="7" aria-valuetext="seven stars"
+ aria-label="slider-role"></div>
<div tabindex="0" role="spinbutton" aria-valuemin="1" aria-valuemax="31"
aria-valuenow="14" id="spinbutton-role"></div>
<div tabindex="0" role="progressbar" aria-valuemin="0" aria-valuenow="0.9"
- aria-valuemax="1.0" id="progressbar-role"></div>
+ aria-valuemax="1.0" aria-label="progressbar-role"></div>
<div tabindex="0" role="scrollbar" aria-valuemin="0" aria-valuenow="0"
aria-valuemax="1.0" aria-orientation="vertical" aria-controls="main"
id="scrollbar-role"></div>
- <div id="main">Content for scrollbar to control</div>
+ <div id="main" aria-label="main">Content for scrollbar to control</div>
- <!-- table and cell mixins -->
+ <!-- table and cell attributes -->
<table id="table" role="grid">
<tr role="row">
<td role="cell">Cell spanning one column</td>
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.html b/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.html
index f60d19c..c2eba0b 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.html
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.html
@@ -4,4 +4,4 @@
* LICENSE file.
-->
<script src="common.js"></script>
-<script src="mixins.js"></script>
+<script src="attributes.js"></script>
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js
index b3ae61b..0e0595e 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js
@@ -2,45 +2,45 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-var ActiveDescendantMixin = [ 'activedescendant' ];
-var LinkMixins = [ 'url' ];
-var DocumentMixins = [ 'docUrl',
- 'docTitle',
- 'docLoaded',
- 'docLoadingProgress' ];
-var ScrollableMixins = [ 'scrollX',
- 'scrollXMin',
- 'scrollXMax',
- 'scrollY',
- 'scrollYMin',
- 'scrollYMax' ];
-var EditableTextMixins = [ 'textSelStart',
- 'textSelEnd' ];
-var RangeMixins = [ 'valueForRange',
- 'minValueForRange',
- 'maxValueForRange' ];
-var TableMixins = [ 'tableRowCount',
- 'tableColumnCount' ];
-var TableCellMixins = [ 'tableCellColumnIndex',
- 'tableCellColumnSpan',
- 'tableCellRowIndex',
- 'tableCellRowSpan' ];
+var ActiveDescendantAttribute = [ 'activedescendant' ];
+var LinkAttributes = [ 'url' ];
+var DocumentAttributes = [ 'docUrl',
+ 'docTitle',
+ 'docLoaded',
+ 'docLoadingProgress' ];
+var ScrollableAttributes = [ 'scrollX',
+ 'scrollXMin',
+ 'scrollXMax',
+ 'scrollY',
+ 'scrollYMin',
+ 'scrollYMax' ];
+var EditableTextAttributes = [ 'textSelStart',
+ 'textSelEnd' ];
+var RangeAttributes = [ 'valueForRange',
+ 'minValueForRange',
+ 'maxValueForRange' ];
+var TableAttributes = [ 'tableRowCount',
+ 'tableColumnCount' ];
+var TableCellAttributes = [ 'tableCellColumnIndex',
+ 'tableCellColumnSpan',
+ 'tableCellRowIndex',
+ 'tableCellRowSpan' ];
var allTests = [
- function testDocumentAndScrollMixins() {
- for (var i = 0; i < DocumentMixins.length; i++) {
- var mixinAttribute = DocumentMixins[i];
- assertTrue(mixinAttribute in rootNode,
- 'rootNode should have a ' + mixinAttribute + ' attribute');
+ function testDocumentAndScrollAttributes() {
+ for (var i = 0; i < DocumentAttributes.length; i++) {
+ var attribute = DocumentAttributes[i];
+ assertTrue(attribute in rootNode,
+ 'rootNode should have a ' + attribute + ' attribute');
}
- for (var i = 0; i < ScrollableMixins.length; i++) {
- var mixinAttribute = ScrollableMixins[i];
- assertTrue(mixinAttribute in rootNode,
- 'rootNode should have a ' + mixinAttribute + ' attribute');
+ for (var i = 0; i < ScrollableAttributes.length; i++) {
+ var attribute = ScrollableAttributes[i];
+ assertTrue(attribute in rootNode,
+ 'rootNode should have a ' + attribute + ' attribute');
}
assertEq(url, rootNode.docUrl);
- assertEq('Automation Tests - Mixin attributes', rootNode.docTitle);
+ assertEq('Automation Tests - Attributes', rootNode.docTitle);
assertEq(true, rootNode.docLoaded);
assertEq(1, rootNode.docLoadingProgress);
assertEq(0, rootNode.scrollX);
@@ -52,22 +52,17 @@ var allTests = [
chrome.test.succeed();
},
- function testActiveDescendantAndOwns() {
+ function testActiveDescendant() {
var combobox = rootNode.find({ role: 'comboBox' });
-
- assertTrue('owns' in combobox, 'combobox should have an owns attribute');
- assertEq(1, combobox.owns.length);
- var listbox = rootNode.find({ role: 'listBox' });
- assertEq(listbox, combobox.owns[0]);
-
assertTrue('activedescendant' in combobox,
'combobox should have an activedescendant attribute');
+ var listbox = rootNode.find({ role: 'listBox' });
var opt6 = listbox.children[5];
assertEq(opt6, combobox.activedescendant);
chrome.test.succeed();
},
- function testLinkMixins() {
+ function testLinkAttributes() {
var links = rootNode.findAll({ role: 'link' });
assertEq(2, links.length);
@@ -78,56 +73,56 @@ var allTests = [
var ariaLink = links[1];
assertEq('ARIA link', ariaLink.name);
- assertTrue('url' in ariaLink, 'ariaLink should have a url attribute');
- assertEq('', ariaLink.url);
+ assertTrue('url' in ariaLink, 'ariaLink should have an empty url');
+ assertEq(undefined, ariaLink.url);
chrome.test.succeed();
},
- function testEditableTextMixins() {
+ function testEditableTextAttributes() {
var textFields = rootNode.findAll({ role: 'textField' });
assertEq(3, textFields.length);
- var EditableTextMixins = [ 'textSelStart', 'textSelEnd' ];
+ var EditableTextAttributes = [ 'textSelStart', 'textSelEnd' ];
for (var i = 0; i < textFields.length; i++) {
var textField = textFields[i];
- var id = textField.attributes.id;
- for (var j = 0; j < EditableTextMixins.length; j++) {
- var mixinAttribute = EditableTextMixins[j];
- assertTrue(mixinAttribute in textField,
- 'textField (' + id + ') should have a ' + mixinAttribute +
- ' attribute');
+ var description = textField.description;
+ for (var j = 0; j < EditableTextAttributes.length; j++) {
+ var attribute = EditableTextAttributes[j];
+ assertTrue(attribute in textField,
+ 'textField (' + description + ') should have a ' +
+ attribute + ' attribute');
}
}
var input = textFields[0];
- assertEq('text-input', input.attributes.id);
+ assertEq('text-input', input.description);
assertEq(2, input.textSelStart);
assertEq(8, input.textSelEnd);
var textArea = textFields[1];
- assertEq('textarea', textArea.attributes.id);
- for (var i = 0; i < EditableTextMixins.length; i++) {
- var mixinAttribute = EditableTextMixins[i];
- assertTrue(mixinAttribute in textArea,
- 'textArea should have a ' + mixinAttribute + ' attribute');
+ assertEq('textarea', textArea.description);
+ for (var i = 0; i < EditableTextAttributes.length; i++) {
+ var attribute = EditableTextAttributes[i];
+ assertTrue(attribute in textArea,
+ 'textArea should have a ' + attribute + ' attribute');
}
assertEq(0, textArea.textSelStart);
assertEq(0, textArea.textSelEnd);
var ariaTextbox = textFields[2];
- assertEq('textbox-role', ariaTextbox.attributes.id);
+ assertEq('textbox-role', ariaTextbox.description);
assertEq(0, ariaTextbox.textSelStart);
assertEq(0, ariaTextbox.textSelEnd);
chrome.test.succeed();
},
- function testRangeMixins() {
+ function testRangeAttributes() {
var sliders = rootNode.findAll({ role: 'slider' });
assertEq(2, sliders.length);
var spinButtons = rootNode.findAll({ role: 'spinButton' });
assertEq(1, spinButtons.length);
var progressIndicators = rootNode.findAll({ role: 'progressIndicator' });
assertEq(1, progressIndicators.length);
- assertEq('progressbar-role', progressIndicators[0].attributes.id);
+ assertEq('progressbar-role', progressIndicators[0].description);
var scrollBars = rootNode.findAll({ role: 'scrollBar' });
assertEq(1, scrollBars.length);
@@ -136,22 +131,22 @@ var allTests = [
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
- for (var j = 0; j < RangeMixins.length; j++) {
- var mixinAttribute = RangeMixins[j];
- assertTrue(mixinAttribute in range,
- range.role + ' (' + range.attributes.id + ') should have a '
- + mixinAttribute + ' attribute');
+ for (var j = 0; j < RangeAttributes.length; j++) {
+ var attribute = RangeAttributes[j];
+ assertTrue(attribute in range,
+ range.role + ' (' + range.description + ') should have a '
+ + attribute + ' attribute');
}
}
var inputRange = sliders[0];
- assertEq('range-input', inputRange.attributes.id);
+ assertEq('range-input', inputRange.description);
assertEq(4, inputRange.valueForRange);
assertEq(0, inputRange.minValueForRange);
assertEq(5, inputRange.maxValueForRange);
var ariaSlider = sliders[1];
- assertEq('slider-role', ariaSlider.attributes.id);
+ assertEq('slider-role', ariaSlider.description);
assertEq(7, ariaSlider.valueForRange);
assertEq(1, ariaSlider.minValueForRange);
assertEq(10, ariaSlider.maxValueForRange);
@@ -172,7 +167,7 @@ var allTests = [
chrome.test.succeed();
},
- function testTableMixins() {
+ function testTableAttributes() {
var table = rootNode.find({ role: 'table' });;
assertEq(3, table.tableRowCount);
assertEq(3, table.tableColumnCount);
@@ -225,22 +220,22 @@ var allTests = [
chrome.test.succeed();
},
- function testNoMixins() {
- var div = rootNode.find({ attributes: { id: 'main' } });
+ function testNoAttributes() {
+ var div = rootNode.find({ attributes: { description: 'main' } });
assertTrue(div !== undefined);
- var allMixins = [].concat(ActiveDescendantMixin,
- LinkMixins,
- DocumentMixins,
- ScrollableMixins,
- EditableTextMixins,
- RangeMixins,
- TableMixins,
- TableCellMixins);
- for (var mixinAttr in allMixins) {
- assertFalse(mixinAttr in div);
+ var allAttributes = [].concat(ActiveDescendantAttribute,
+ LinkAttributes,
+ DocumentAttributes,
+ ScrollableAttributes,
+ EditableTextAttributes,
+ RangeAttributes,
+ TableAttributes,
+ TableCellAttributes);
+ for (var attributeAttr in allAttributes) {
+ assertFalse(attributeAttr in div);
}
chrome.test.succeed();
}
];
-setUpAndRunTests(allTests, 'mixins.html');
+setUpAndRunTests(allTests, 'attributes.html');
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js
index 64c4e36..c45bd6e 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js
@@ -5,48 +5,47 @@
// Do not test orientation or hover attributes (similar to exclusions on native
// accessibility), since they can be inconsistent depending on the environment.
var RemoveUntestedStates = function(state) {
- delete state[StateType.horizontal];
- delete state[StateType.hovered];
- delete state[StateType.vertical];
+ var result = JSON.parse(JSON.stringify(state));
+ delete result[StateType.horizontal];
+ delete result[StateType.hovered];
+ delete result[StateType.vertical];
+ return result;
};
var allTests = [
function testSimplePage() {
var title = rootNode.docTitle;
assertEq('Automation Tests', title);
- RemoveUntestedStates(rootNode.state);
+
+ var state = RemoveUntestedStates(rootNode.state);
assertEq(
- {enabled: true, focusable: true, readOnly: true},
- rootNode.state);
+ {enabled: true, focusable: true, readOnly: true},
+ state);
+
var children = rootNode.children;
assertEq(RoleType.rootWebArea, rootNode.role);
assertEq(1, children.length);
var body = children[0];
assertEq('body', body.htmlTag);
-
- RemoveUntestedStates(body.state);
- assertEq({enabled: true, readOnly: true},
- body.state);
+ state = RemoveUntestedStates(body.state);
+ assertEq({enabled: true, readOnly: true}, state);
var contentChildren = body.children;
assertEq(3, contentChildren.length);
var okButton = contentChildren[0];
assertEq('Ok', okButton.name);
- RemoveUntestedStates(okButton.state);
- assertEq({enabled: true, focusable: true, readOnly: true},
- okButton.state);
+ state = RemoveUntestedStates(okButton.state);
+ assertEq({enabled: true, focusable: true, readOnly: true}, state);
var userNameInput = contentChildren[1];
assertEq('Username',
userNameInput.description);
- RemoveUntestedStates(userNameInput.state);
- assertEq({enabled: true, focusable: true},
- userNameInput.state);
+ state = RemoveUntestedStates(userNameInput.state);
+ assertEq({enabled: true, focusable: true}, state);
var cancelButton = contentChildren[2];
assertEq('Cancel',
cancelButton.name);
- RemoveUntestedStates(cancelButton.state);
- assertEq({enabled: true, focusable: true, readOnly: true},
- cancelButton.state);
+ state = RemoveUntestedStates(cancelButton.state);
+ assertEq({enabled: true, focusable: true, readOnly: true}, state);
// Traversal.
assertEq(undefined, rootNode.parent);
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/tree_change.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/tree_change.js
index dcb1861b..19a1416 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/tree_change.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/tree_change.js
@@ -5,7 +5,7 @@
var allTests = [
function testTreeChangedObserverForCreatingNode() {
chrome.automation.addTreeChangeObserver(function(change) {
- if (change.type == "nodeCreated" && change.target.name == "New") {
+ if (change.type == "subtreeCreated" && change.target.name == "New") {
chrome.test.succeed();
}
});
diff --git a/chrome/test/data/extensions/api_test/automation/tests/unit/manifest.json b/chrome/test/data/extensions/api_test/automation/tests/unit/manifest.json
deleted file mode 100644
index 867927b..0000000
--- a/chrome/test/data/extensions/api_test/automation/tests/unit/manifest.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "name": "chrome.automation.unit",
- "version": "0.1",
- "manifest_version": 2,
- "description": "Unittests for chrome.automation.",
- "permissions": ["tabs", "http://a.com/"],
- "automation": true
-}
diff --git a/chrome/test/data/extensions/api_test/automation/tests/unit/test.js b/chrome/test/data/extensions/api_test/automation/tests/unit/test.js
deleted file mode 100644
index cc788ec..0000000
--- a/chrome/test/data/extensions/api_test/automation/tests/unit/test.js
+++ /dev/null
@@ -1,508 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-chrome.test.runWithNativesEnabled(function() {
- var moduleSystem = chrome.test.getModuleSystem(window);
- window.AutomationRootNode =
- moduleSystem.require('automationNode').AutomationRootNode;
- window.privates = moduleSystem.privates;
- // Unused.
- window.automationUtil = function() {};
- window.automationUtil.storeTreeCallback = function() {};
- window.automationUtil.treeChangeObservers = [];
-});
-
-var assertEq = chrome.test.assertEq;
-var assertFalse = chrome.test.assertFalse;
-var assertTrue = chrome.test.assertTrue;
-var assertIsDef = function(obj) {
- assertTrue(obj !== null && obj !== undefined);
-}
-var assertIsNotDef = function(obj) {
- assertTrue(obj === null || obj === undefined);
-}
-var succeed = chrome.test.succeed;
-
-var tests = [
- function testAutomationRootNode() {
- var root = new AutomationRootNode();
- assertTrue(root.isRootNode);
-
- succeed();
- },
-
- function testAriaRelationshipAttributes() {
- var data = {
- 'eventType': 'loadComplete',
- 'processID': 17, 'routingID': 2, 'targetID': 1,
- 'update': { 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': {'docLoaded': true },
- 'childIds': [5, 6, 7, 8, 9, 10, 11]
- },
- {
- 'id': 11, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': {'id': 'target' }
- },
- {
- 'id': 5, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': {'aria-activedescendant': 'target',
- 'id': 'activedescendant'},
- 'intAttributes': {'activedescendantId': 11},
- },
- {
- 'id': 6, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': {'aria-controls': 'target', 'id': 'controlledBy'},
- 'intlistAttributes': {'controlsIds': [11]},
- },
- {
- 'id': 7, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': {'aria-describedby': 'target', 'id': 'describedBy'},
- 'intlistAttributes': {'describedbyIds': [11, 6]},
- },
- {
- 'id': 8, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': {'aria-flowto': 'target', 'id': 'flowTo'},
- 'intlistAttributes': {'flowtoIds': [11]},
- },
- {
- 'id': 9, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': {'aria-labelledby': 'target', 'id': 'labelledBy'},
- 'intlistAttributes': {'labelledbyIds': [11]}
- },
- {
- 'id': 10, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': {'aria-owns': 'target', 'id': 'owns'},
- 'intlistAttributes': {'ownsIds': [11]},
- }
- ]}
- }
-
- var tree = new AutomationRootNode();
- try {
- privates(tree).impl.onAccessibilityEvent(data);
- } catch (e) {
- console.log(e.stack);
- }
-
- var activedescendant = tree.firstChild;
- assertIsDef(activedescendant);
- assertEq('activedescendant', activedescendant.attributes.id);
- assertEq(
- 'target',
- activedescendant.attributes['aria-activedescendant'].attributes.id);
-
- assertFalse(activedescendant.activedescendant == null,
- 'activedescendant should not be null');
- assertEq(
- 'target',
- activedescendant.activedescendant.attributes.id);
- assertIsNotDef(activedescendant.attributes.activedescendantId);
-
- var controlledBy = activedescendant.nextSibling;
- assertIsDef(controlledBy);
- assertEq('controlledBy', controlledBy.attributes.id);
- assertEq(1, controlledBy.attributes['aria-controls'].length);
- assertEq('target',
- controlledBy.attributes['aria-controls'][0].attributes.id);
- assertEq(1, controlledBy.controls.length);
- assertEq('target', controlledBy.controls[0].attributes.id);
- assertIsNotDef(controlledBy.attributes.controlledbyIds);
-
- var describedBy = controlledBy.nextSibling;
- assertIsDef(describedBy);
- assertEq('describedBy', describedBy.attributes.id);
- assertEq(2, describedBy.attributes['aria-describedby'].length);
- assertEq('target',
- describedBy.attributes['aria-describedby'][0].attributes.id);
- assertEq('controlledBy',
- describedBy.attributes['aria-describedby'][1].attributes.id);
- assertEq(2, describedBy.describedby.length);
- assertEq('target', describedBy.describedby[0].attributes.id);
- assertEq('controlledBy',
- describedBy.describedby[1].attributes.id);
- assertIsNotDef(describedBy.attributes.describedbyIds);
-
- var flowTo = describedBy.nextSibling;
- assertIsDef(flowTo);
- assertEq('flowTo', flowTo.attributes.id);
- assertEq(1, flowTo.attributes['aria-flowto'].length);
- assertEq('target',
- flowTo.attributes['aria-flowto'][0].attributes.id);
- assertEq(1, flowTo.flowto.length);
- assertEq('target', flowTo.flowto[0].attributes.id);
- assertIsNotDef(flowTo.attributes.flowtoIds);
-
- var labelledBy = flowTo.nextSibling;
- assertIsDef(labelledBy);
- assertEq('labelledBy', labelledBy.attributes.id);
- assertEq(1, labelledBy.attributes['aria-labelledby'].length);
- assertEq('target',
- labelledBy.attributes['aria-labelledby'][0].attributes.id);
- assertEq(1, labelledBy.labelledby.length);
- assertEq('target',
- labelledBy.labelledby[0].attributes.id);
- assertIsNotDef(labelledBy.attributes.labelledbyIds);
-
- var owns = labelledBy.nextSibling;
- assertIsDef(owns);
- assertEq('owns', owns.attributes.id);
- assertEq(1, owns.attributes['aria-owns'].length);
- assertEq('target', owns.attributes['aria-owns'][0].attributes.id);
- assertEq(1, owns.owns.length);
- assertEq('target', owns.owns[0].attributes.id);
- assertIsNotDef(owns.attributes.ownsIds);
-
- succeed();
- },
-
- function testCannotSetAttribute() {
- var update =
- {
- 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': {'docLoaded': true},
- 'childIds': [11]
- },
- {
- 'id': 11, 'role': 'button',
- 'stringAttributes': {'name': 'foo'},
- 'childIds': []
- }]
- }
-
- var tree = new AutomationRootNode();
- assertTrue(privates(tree).impl.unserialize(update));
- var button = tree.firstChild;
- assertEq('button', button.role);
- assertEq('foo', button.name);
- button.name = 'bar';
- assertEq('foo', button.name);
-
- succeed();
- },
-
- function testBadUpdateInvalidChildIds() {
- var update =
- {
- 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': {'docLoaded': true},
- 'childIds': [5, 6, 7, 8, 9, 10, 11]
- }]
- }
-
- var tree = new AutomationRootNode();
-
- // This is a bad update because the root references non existent child ids.
- assertFalse(privates(tree).impl.unserialize(update));
-
- succeed();
- },
-
- function testMultipleUpdateNameChanged() {
- var update =
- {
- 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': {'docLoaded': true},
- 'childIds': [11]
- },
- {
- 'id': 11, 'role': 'button',
- 'stringAttributes': {'name': 'foo'},
- 'childIds': []
- }]
- }
-
- // First, setup the initial tree.
- var tree = new AutomationRootNode();
- assertTrue(privates(tree).impl.unserialize(update));
- var button = tree.firstChild;
- assertEq('button', button.role);
- assertEq('foo', button.name);
-
- // Now, apply an update that changes the button's name.
- // Remove the root since the native serializer stops at the LCA.
- update.nodes.splice(0, 1);
- update.nodes[0].stringAttributes.name = 'bar';
-
- // Make sure the name changes.
- assertTrue(privates(tree).impl.unserialize(update));
- assertEq('bar', button.name);
-
- succeed();
- },
-
- function testDocumentAndScrollableMixins() {
- var update = { 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': { 'docLoaded': false },
- 'stringAttributes': { 'docUrl': 'chrome://terms',
- 'docTitle': 'Google Chrome Terms of Service' },
- 'intAttributes': { 'scrollY': 583,
- 'scrollYMax': 9336 },
- 'floatAttributes': { 'docLoadingProgress': 0.9 },
- 'childIds': [2]
- },
- {
- 'id': 2, 'role': 'div',
- 'childIds': [],
- 'htmlAttributes': { 'id': 'child' },
- },
- ] };
-
- var tree = new AutomationRootNode();
- assertTrue(privates(tree).impl.unserialize(update));
- assertEq(false, tree.docLoaded);
- assertEq('chrome://terms', tree.docUrl);
- assertEq('Google Chrome Terms of Service', tree.docTitle);
- assertEq('0.9', tree.docLoadingProgress.toPrecision(1));
- assertEq(583, tree.scrollY);
- assertEq(9336, tree.scrollYMax);
- // Default values will be set for mixin attributes even if not in data.
- assertEq(0, tree.scrollYMin);
- assertEq(0, tree.scrollX);
- assertEq(0, tree.scrollXMin);
- assertEq(0, tree.scrollXMax);
-
- succeed();
- },
-
- function testEditableTextMixins() {
- var update = { 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': { 'docLoaded': true },
- 'stringAttributes': { 'docUrl': 'chrome://terms',
- 'docTitle': 'Google Chrome Terms of Service' },
- 'intAttributes': { 'scrollY': 583,
- 'scrollYMax': 9336 },
- 'childIds': [2, 3]
- },
- {
- 'id': 2, 'role': 'textField',
- 'intAttributes': { 'textSelStart': 10, 'textSelEnd': 20 },
- 'childIds': []
- },
- {
- 'id': 3, 'role': 'textField',
- 'childIds': []
- },
-
- ] };
-
- var tree = new AutomationRootNode();
- assertTrue(privates(tree).impl.unserialize(update));
- assertEq(true, tree.docLoaded);
- assertFalse('textSelStart' in tree);
- assertFalse('textSelEnd' in tree);
- var textField = tree.firstChild;
- assertEq(10, textField.textSelStart);
- assertEq(20, textField.textSelEnd);
- var textArea = textField.nextSibling;
- assertEq(-1, textArea.textSelStart);
- assertEq(-1, textArea.textSelEnd);
-
- succeed();
- },
-
- function testRangeMixins() {
- var update = { 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': { 'docLoaded': true },
- 'stringAttributes': { 'docUrl': 'chrome://terms',
- 'docTitle': 'Google Chrome Terms of Service' },
- 'intAttributes': { 'scrollY': 583,
- 'scrollYMax': 9336 },
- 'childIds': [2, 3, 4, 5]
- },
- {
- 'id': 2, 'role': 'progressIndicator',
- 'floatAttributes': { 'valueForRange': 1.0,
- 'minValueForRange': 0.0,
- 'maxValueForRange': 1.0 },
- 'childIds': []
- },
- {
- 'id': 3, 'role': 'scrollBar',
- 'floatAttributes': { 'valueForRange': 0.3,
- 'minValueForRange': 0.0,
- 'maxValueForRange': 1.0 },
- 'childIds': []
- },
- {
- 'id': 4, 'role': 'slider',
- 'floatAttributes': { 'valueForRange': 3.0,
- 'minValueForRange': 1.0,
- 'maxValueForRange': 5.0 },
- 'childIds': []
- },
- {
- 'id': 5, 'role': 'spinButton',
- 'floatAttributes': { 'valueForRange': 14.0,
- 'minValueForRange': 1.0,
- 'maxValueForRange': 31.0 },
- 'childIds': []
- }
- ] };
-
- var tree = new AutomationRootNode();
- assertTrue(privates(tree).impl.unserialize(update));
- assertEq(true, tree.docLoaded);
- assertFalse('valueForRange' in tree);
- assertFalse('minValueForRange' in tree);
- assertFalse('maxValueForRange' in tree);
-
- var progressIndicator = tree.firstChild;
- assertEq(1.0, progressIndicator.valueForRange);
- assertEq(0.0, progressIndicator.minValueForRange);
- assertEq(1.0, progressIndicator.maxValueForRange);
-
- var scrollBar = progressIndicator.nextSibling;
- assertEq(0.3, scrollBar.valueForRange);
- assertEq(0.0, scrollBar.minValueForRange);
- assertEq(1.0, scrollBar.maxValueForRange);
-
- var slider = scrollBar.nextSibling;
- assertEq(3.0, slider.valueForRange);
- assertEq(1.0, slider.minValueForRange);
- assertEq(5.0, slider.maxValueForRange);
-
- var spinButton = slider.nextSibling;
- assertEq(14.0, spinButton.valueForRange);
- assertEq(1.0, spinButton.minValueForRange);
- assertEq(31.0, spinButton.maxValueForRange);
-
- succeed();
- },
-
- function testTableMixins() {
- var update = { 'nodeIdToClear': 0, 'nodes': [
- {
- 'id': 1, 'role': 'rootWebArea',
- 'boolAttributes': { 'docLoaded': true },
- 'stringAttributes': { 'docUrl': 'chrome://terms',
- 'docTitle': 'Google Chrome Terms of Service' },
- 'intAttributes': { 'scrollY': 583,
- 'scrollYMax': 9336 },
- 'childIds': [2]
- },
- {
- 'id': 2, 'role': 'table',
- 'childIds': [3, 6],
- 'intAttributes': { tableRowCount: 2, tableColumnCount: 3 }
- },
- {
- 'id': 3, 'role': 'row',
- 'childIds': [4, 5]
- },
- {
- 'id': 4, 'role': 'cell',
- 'intAttributes': { 'tableCellColumnIndex': 0,
- 'tableCellColumnSpan': 2,
- 'tableCellRowIndex': 0,
- 'tableCellRowSpan': 1 },
- 'childIds': []
- },
- {
- 'id': 5, 'role': 'cell',
- 'intAttributes': { 'tableCellColumnIndex': 2,
- 'tableCellColumnSpan': 1,
- 'tableCellRowIndex': 0,
- 'tableCellRowSpan': 2 },
- 'childIds': []
- },
- {
- 'id': 6, 'role': 'row',
- 'childIds': [7, 8]
- },
- {
- 'id': 7, 'role': 'cell',
- 'intAttributes': { 'tableCellColumnIndex': 0,
- 'tableCellColumnSpan': 1,
- 'tableCellRowIndex': 1,
- 'tableCellRowSpan': 1 },
- 'childIds': []
- },
- {
- 'id': 8, 'role': 'cell',
- 'intAttributes': { 'tableCellColumnIndex': 1,
- 'tableCellColumnSpan': 1,
- 'tableCellRowIndex': 1,
- 'tableCellRowSpan': 1 },
- 'childIds': []
- }
- ] };
-
- var tree = new AutomationRootNode();
- try {
- assertTrue(privates(tree).impl.unserialize(update));
- } catch (e) {
- console.log(e.stack);
- }
- var TableMixinAttributes = {
- tableRowCount: 0,
- tableColumnCount: 0
- };
- for (var attribute in TableMixinAttributes)
- assertFalse(attribute in tree);
-
- var TableCellMixinAttributes = {
- tableCellColumnIndex: 0,
- tableCellColumnSpan: 1,
- tableCellRowIndex: 0,
- tableCellRowSpan: 1
- };
- for (var attribute in TableCellMixinAttributes)
- assertFalse(attribute in tree);
-
- var table = tree.firstChild;
- assertEq(2, table.tableRowCount);
- assertEq(3, table.tableColumnCount);
-
- var row1 = table.firstChild;
- var cell1 = row1.firstChild;
- assertEq(0, cell1.tableCellColumnIndex);
- assertEq(2, cell1.tableCellColumnSpan);
- assertEq(0, cell1.tableCellRowIndex);
- assertEq(1, cell1.tableCellRowSpan);
-
- var cell2 = cell1.nextSibling;
- assertEq(2, cell2.tableCellColumnIndex);
- assertEq(1, cell2.tableCellColumnSpan);
- assertEq(0, cell2.tableCellRowIndex);
- assertEq(2, cell2.tableCellRowSpan);
-
- var row2 = row1.nextSibling;
- var cell3 = row2.firstChild;
- assertEq(0, cell3.tableCellColumnIndex);
- assertEq(1, cell3.tableCellColumnSpan);
- assertEq(1, cell3.tableCellRowIndex);
- assertEq(1, cell3.tableCellRowSpan);
-
- var cell4 = cell3.nextSibling;
- assertEq(1, cell4.tableCellColumnIndex);
- assertEq(1, cell4.tableCellColumnSpan);
- assertEq(1, cell4.tableCellRowIndex);
- assertEq(1, cell4.tableCellRowSpan);
-
- succeed();
- }
-];
-
-chrome.test.runTests(tests);
diff --git a/chrome/test/data/extensions/api_test/automation/tests/unit/unit.html b/chrome/test/data/extensions/api_test/automation/tests/unit/unit.html
deleted file mode 100644
index 77bad90..0000000
--- a/chrome/test/data/extensions/api_test/automation/tests/unit/unit.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!--
- * Copyright 2014 The Chromium Authors. All rights reserved. Use of this
- * source code is governed by a BSD-style license that can be found in the
- * LICENSE file.
--->
-<script src="test.js"></script>
diff --git a/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/content/browser/accessibility/browser_accessibility_manager_unittest.cc
index d30c258..c9e6d83 100644
--- a/content/browser/accessibility/browser_accessibility_manager_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_unittest.cc
@@ -90,13 +90,15 @@ class TestBrowserAccessibilityDelegate
return gfx::kNullAcceleratedWidget;
}
gfx::NativeViewAccessible AccessibilityGetNativeViewAccessible() override {
- return NULL;
+ return nullptr;
}
BrowserAccessibilityManager* AccessibilityGetChildFrame(
int accessibility_node_id) override {
- return NULL;
+ return nullptr;
+ }
+ BrowserAccessibility* AccessibilityGetParentFrame() override {
+ return nullptr;
}
- BrowserAccessibility* AccessibilityGetParentFrame() override { return NULL; }
void AccessibilityGetAllChildFrames(
std::vector<BrowserAccessibilityManager*>* child_frames) override {}
@@ -141,7 +143,7 @@ TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
BrowserAccessibilityManager* manager =
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, button, checkbox),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
@@ -155,7 +157,7 @@ TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
manager =
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, button, checkbox),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
@@ -246,7 +248,7 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) {
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(tree1_root,
tree1_child1, tree1_child2, tree1_child3),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
@@ -422,7 +424,7 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) {
tree1_child1, tree1_grandchild1,
tree1_child2, tree1_grandchild2,
tree1_child3, tree1_grandchild3),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
@@ -550,7 +552,7 @@ TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) {
BrowserAccessibilityManager* manager =
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(tree1_1, tree1_2, tree1_3, tree1_4),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
@@ -684,7 +686,7 @@ TEST(BrowserAccessibilityManagerTest, BoundsForRange) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -772,7 +774,7 @@ TEST(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -832,7 +834,7 @@ TEST(BrowserAccessibilityManagerTest, BoundsForRangeScrolledWindow) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, static_text, inline_text),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -918,7 +920,7 @@ TEST(BrowserAccessibilityManagerTest, MAYBE_BoundsForRangeOnParentElement) {
MakeAXTreeUpdate(
root, div, static_text1, img,
static_text2, inline_text1, inline_text2),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -965,7 +967,7 @@ TEST(BrowserAccessibilityManagerTest, NextPreviousInTreeOrder) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, node2, node3, node4, node5),
- NULL,
+ nullptr,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -975,18 +977,69 @@ TEST(BrowserAccessibilityManagerTest, NextPreviousInTreeOrder) {
node3_accessible->PlatformGetChild(0);
BrowserAccessibility* node5_accessible = root_accessible->PlatformGetChild(2);
- ASSERT_EQ(NULL, manager->NextInTreeOrder(NULL));
+ ASSERT_EQ(nullptr, manager->NextInTreeOrder(nullptr));
ASSERT_EQ(node2_accessible, manager->NextInTreeOrder(root_accessible));
ASSERT_EQ(node3_accessible, manager->NextInTreeOrder(node2_accessible));
ASSERT_EQ(node4_accessible, manager->NextInTreeOrder(node3_accessible));
ASSERT_EQ(node5_accessible, manager->NextInTreeOrder(node4_accessible));
- ASSERT_EQ(NULL, manager->NextInTreeOrder(node5_accessible));
+ ASSERT_EQ(nullptr, manager->NextInTreeOrder(node5_accessible));
- ASSERT_EQ(NULL, manager->PreviousInTreeOrder(NULL));
+ ASSERT_EQ(nullptr, manager->PreviousInTreeOrder(nullptr));
ASSERT_EQ(node4_accessible, manager->PreviousInTreeOrder(node5_accessible));
ASSERT_EQ(node3_accessible, manager->PreviousInTreeOrder(node4_accessible));
ASSERT_EQ(node2_accessible, manager->PreviousInTreeOrder(node3_accessible));
ASSERT_EQ(root_accessible, manager->PreviousInTreeOrder(node2_accessible));
}
+TEST(BrowserAccessibilityManagerTest, DeletingFocusedNodeDoesNotCrash) {
+ // Create a really simple tree with one root node and one focused child.
+ ui::AXNodeData root;
+ root.id = 1;
+ root.role = ui::AX_ROLE_ROOT_WEB_AREA;
+ root.state = 0;
+ root.child_ids.push_back(2);
+
+ ui::AXNodeData node2;
+ node2.id = 2;
+ node2.state = 1 << ui::AX_STATE_FOCUSED;
+
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ MakeAXTreeUpdate(root, node2),
+ nullptr,
+ new CountedBrowserAccessibilityFactory()));
+
+ ASSERT_EQ(1, manager->GetRoot()->GetId());
+ ASSERT_EQ(1, manager->GetFocus(manager->GetRoot())->GetId());
+
+ // Send the focus event for node 2.
+ std::vector<AccessibilityHostMsg_EventParams> events;
+ events.push_back(AccessibilityHostMsg_EventParams());
+ events[0].update = MakeAXTreeUpdate(node2);
+ events[0].id = 2;
+ events[0].event_type = ui::AX_EVENT_FOCUS;
+ manager->OnAccessibilityEvents(events);
+
+ ASSERT_EQ(1, manager->GetRoot()->GetId());
+ ASSERT_EQ(2, manager->GetFocus(manager->GetRoot())->GetId());
+
+ // Now replace the tree with a new tree consisting of a single root.
+ ui::AXNodeData root2;
+ root2.id = 3;
+ root2.role = ui::AX_ROLE_ROOT_WEB_AREA;
+ root2.state = 0;
+
+ std::vector<AccessibilityHostMsg_EventParams> events2;
+ events2.push_back(AccessibilityHostMsg_EventParams());
+ events2[0].update = MakeAXTreeUpdate(root2);
+ events2[0].id = -1;
+ events2[0].event_type = ui::AX_EVENT_NONE;
+ manager->OnAccessibilityEvents(events2);
+
+ // Make sure that the focused node was updated to the new root and
+ // that this doesn't crash.
+ ASSERT_EQ(3, manager->GetRoot()->GetId());
+ ASSERT_EQ(3, manager->GetFocus(manager->GetRoot())->GetId());
+}
+
} // namespace content
diff --git a/extensions/renderer/logging_native_handler.cc b/extensions/renderer/logging_native_handler.cc
index 8a913a1..86fbc9c 100644
--- a/extensions/renderer/logging_native_handler.cc
+++ b/extensions/renderer/logging_native_handler.cc
@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "extensions/renderer/logging_native_handler.h"
-
#include "base/logging.h"
#include "base/strings/stringprintf.h"
+#include "extensions/renderer/logging_native_handler.h"
+#include "extensions/renderer/script_context.h"
namespace extensions {
@@ -72,31 +72,7 @@ void LoggingNativeHandler::ParseArgs(
*error_message = "Error: " + std::string(*v8::String::Utf8Value(args[1]));
}
- v8::Local<v8::StackTrace> stack_trace =
- v8::StackTrace::CurrentStackTrace(args.GetIsolate(), 10);
- if (stack_trace.IsEmpty() || stack_trace->GetFrameCount() <= 0) {
- *error_message += "\n <no stack trace>";
- } else {
- for (size_t i = 0; i < (size_t)stack_trace->GetFrameCount(); ++i) {
- v8::Local<v8::StackFrame> frame = stack_trace->GetFrame(i);
- CHECK(!frame.IsEmpty());
- *error_message += base::StringPrintf(
- "\n at %s (%s:%d:%d)",
- ToStringOrDefault(frame->GetFunctionName(), "<anonymous>").c_str(),
- ToStringOrDefault(frame->GetScriptName(), "<anonymous>").c_str(),
- frame->GetLineNumber(),
- frame->GetColumn());
- }
- }
-}
-
-std::string LoggingNativeHandler::ToStringOrDefault(
- const v8::Local<v8::String>& v8_string,
- const std::string& dflt) {
- if (v8_string.IsEmpty())
- return dflt;
- std::string ascii_value = *v8::String::Utf8Value(v8_string);
- return ascii_value.empty() ? dflt : ascii_value;
+ *error_message += "\n" + context()->GetStackTraceAsString();
}
} // namespace extensions
diff --git a/extensions/renderer/logging_native_handler.h b/extensions/renderer/logging_native_handler.h
index e9f4f20..ca9938c 100644
--- a/extensions/renderer/logging_native_handler.h
+++ b/extensions/renderer/logging_native_handler.h
@@ -46,9 +46,6 @@ class LoggingNativeHandler : public ObjectBackedNativeHandler {
void ParseArgs(const v8::FunctionCallbackInfo<v8::Value>& args,
bool* check_value,
std::string* error_message);
-
- std::string ToStringOrDefault(const v8::Local<v8::String>& v8_string,
- const std::string& dflt);
};
} // namespace extensions
diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc
index 58f1b4c..9321468 100644
--- a/extensions/renderer/script_context.cc
+++ b/extensions/renderer/script_context.cc
@@ -58,6 +58,15 @@ std::string GetContextTypeDescriptionString(Feature::Context context_type) {
return std::string();
}
+static std::string ToStringOrDefault(
+ const v8::Local<v8::String>& v8_string,
+ const std::string& dflt) {
+ if (v8_string.IsEmpty())
+ return dflt;
+ std::string ascii_value = *v8::String::Utf8Value(v8_string);
+ return ascii_value.empty() ? dflt : ascii_value;
+}
+
} // namespace
// A gin::Runner that delegates to its ScriptContext.
@@ -373,6 +382,27 @@ std::string ScriptContext::GetDebugString() const {
GetEffectiveContextTypeDescription().c_str());
}
+std::string ScriptContext::GetStackTraceAsString() const {
+ v8::Local<v8::StackTrace> stack_trace =
+ v8::StackTrace::CurrentStackTrace(isolate(), 10);
+ if (stack_trace.IsEmpty() || stack_trace->GetFrameCount() <= 0) {
+ return " <no stack trace>";
+ } else {
+ std::string result;
+ for (int i = 0; i < stack_trace->GetFrameCount(); ++i) {
+ v8::Local<v8::StackFrame> frame = stack_trace->GetFrame(i);
+ CHECK(!frame.IsEmpty());
+ result += base::StringPrintf(
+ "\n at %s (%s:%d:%d)",
+ ToStringOrDefault(frame->GetFunctionName(), "<anonymous>").c_str(),
+ ToStringOrDefault(frame->GetScriptName(), "<anonymous>").c_str(),
+ frame->GetLineNumber(),
+ frame->GetColumn());
+ }
+ return result;
+ }
+}
+
ScriptContext::Runner::Runner(ScriptContext* context) : context_(context) {
}
diff --git a/extensions/renderer/script_context.h b/extensions/renderer/script_context.h
index b6e49b9..4a9531a 100644
--- a/extensions/renderer/script_context.h
+++ b/extensions/renderer/script_context.h
@@ -178,6 +178,9 @@ class ScriptContext : public RequestSender::Source {
// Returns a string representation of this ScriptContext, for debugging.
std::string GetDebugString() const;
+ // Gets the current stack trace as a multi-line string to be logged.
+ std::string GetStackTraceAsString() const;
+
private:
class Runner;
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index d07f239..60fa888 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -204,9 +204,12 @@ bool AXTree::UpdateNode(const AXNodeData& src,
// Update the root of the tree if needed.
if ((src.role == AX_ROLE_ROOT_WEB_AREA || src.role == AX_ROLE_DESKTOP) &&
(!root_ || root_->id() != src.id)) {
- if (root_)
- DestroySubtree(root_, update_state);
+ // Make sure root_ always points to something valid, even inside
+ // DestroySubtree.
+ AXNode* old_root = root_;
root_ = node;
+ if (old_root)
+ DestroySubtree(old_root, update_state);
}
return success;
@@ -221,11 +224,11 @@ void AXTree::DestroySubtree(AXNode* node,
void AXTree::DestroyNodeAndSubtree(AXNode* node,
AXTreeUpdateState* update_state) {
+ if (delegate_)
+ delegate_->OnNodeWillBeDeleted(this, node);
id_map_.erase(node->id());
for (int i = 0; i < node->child_count(); ++i)
DestroyNodeAndSubtree(node->ChildAtIndex(i), update_state);
- if (delegate_)
- delegate_->OnNodeWillBeDeleted(this, node);
if (update_state) {
update_state->pending_nodes.erase(node);
}
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc
index 40aeeff..77ef8cd 100644
--- a/ui/accessibility/ax_tree_unittest.cc
+++ b/ui/accessibility/ax_tree_unittest.cc
@@ -325,8 +325,8 @@ TEST(AXTreeTest, TreeDelegateIsCalled) {
EXPECT_TRUE(tree.Unserialize(update));
ASSERT_EQ(2U, fake_delegate.deleted_ids().size());
- EXPECT_EQ(2, fake_delegate.deleted_ids()[0]);
- EXPECT_EQ(1, fake_delegate.deleted_ids()[1]);
+ EXPECT_EQ(1, fake_delegate.deleted_ids()[0]);
+ EXPECT_EQ(2, fake_delegate.deleted_ids()[1]);
ASSERT_EQ(1U, fake_delegate.subtree_deleted_ids().size());
EXPECT_EQ(1, fake_delegate.subtree_deleted_ids()[0]);