summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorasvitkine <asvitkine@chromium.org>2015-07-23 07:37:51 -0700
committerCommit bot <commit-bot@chromium.org>2015-07-23 14:38:33 +0000
commit3cd234a649e3922895a6bb2e4ec786032034bb97 (patch)
treeb92b8518009b8199c15234a7cd8090abfc158d7e
parent3617707ed710a4e88bcb4a11965d6c0296bf0309 (diff)
downloadchromium_src-3cd234a649e3922895a6bb2e4ec786032034bb97.zip
chromium_src-3cd234a649e3922895a6bb2e4ec786032034bb97.tar.gz
chromium_src-3cd234a649e3922895a6bb2e4ec786032034bb97.tar.bz2
Revert of Re-land: Reimplement automation API on top of C++-backed AXTree. (patchset #6 id:100001 of https://codereview.chromium.org/1231603009/)
Reason for revert: Top crasher in today's canary - 74% of browser crashes. Reverting per stability sheriff guidelines (go/stability-sheriff). Original issue's description: > 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 > > Committed: https://crrev.com/1777fbdbd234ddc8e19d4ba3a42cfd3234b8a158 > Cr-Commit-Position: refs/heads/master@{#339929} TBR=dcheng@chromium.org,aboxhall@chromium.org,dtseng@chromium.org,kalman@chromium.org,dmazzoni@chromium.org NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=495323,502311 Review URL: https://codereview.chromium.org/1251723003 Cr-Commit-Position: refs/heads/master@{#340091}
-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.cc213
-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/mixins.html (renamed from chrome/test/data/extensions/api_test/automation/sites/attributes.html)29
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.html (renamed from chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.html)2
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.js (renamed from chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.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, 2152 insertions, 1550 deletions
diff --git a/chrome/browser/extensions/api/automation/automation_apitest.cc b/chrome/browser/extensions/api/automation/automation_apitest.cc
index 5f1adcf..8505161 100644
--- a/chrome/browser/extensions/api/automation/automation_apitest.cc
+++ b/chrome/browser/extensions/api/automation/automation_apitest.cc
@@ -8,15 +8,13 @@
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/thread_task_runner_handle.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/api/automation_internal/automation_util.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"
@@ -101,6 +99,11 @@ 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"))
@@ -200,9 +203,10 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) {
<< message_;
}
-IN_PROC_BROWSER_TEST_F(AutomationApiTest, Attributes) {
+// Flaky. http://crbug.com/467921
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_Mixins) {
StartEmbeddedTestServer();
- ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "attributes.html"))
+ ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "mixins.html"))
<< message_;
}
@@ -212,4 +216,328 @@ 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 89ae0c7..c62a3fa 100644
--- a/chrome/browser/extensions/api/automation_internal/automation_event_router.cc
+++ b/chrome/browser/extensions/api/automation_internal/automation_event_router.cc
@@ -62,8 +62,7 @@ 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));
}
}
@@ -94,8 +93,7 @@ void AutomationEventRouter::Register(
auto iter = std::find_if(
listeners_.begin(),
listeners_.end(),
- [listener_process_id, listener_routing_id](
- const AutomationListener& item) {
+ [listener_process_id, listener_routing_id](AutomationListener& item) {
return (item.process_id == listener_process_id &&
item.routing_id == listener_routing_id);
});
@@ -134,8 +132,8 @@ void AutomationEventRouter::Observe(
std::remove_if(
listeners_.begin(),
listeners_.end(),
- [process_id](const AutomationListener& item) {
- return item.process_id == process_id;
+ [process_id](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 58efba5..5f2e8c8 100644
--- a/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
+++ b/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
@@ -12,6 +12,7 @@
#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"
@@ -22,16 +23,12 @@
#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"
@@ -48,7 +45,6 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
namespace extensions {
namespace {
-
const int kDesktopTreeID = 0;
const char kCannotRequestAutomationOnPage[] =
"Cannot request automation tree on url \"*\". "
@@ -197,66 +193,24 @@ class AutomationWebContentsObserver
void AccessibilityEventReceived(
const std::vector<content::AXEventNotificationDetails>& details)
override {
- 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);
- }
+ automation_util::DispatchAccessibilityEventsToAutomation(
+ details, browser_context_,
+ web_contents()->GetContainerBounds().OffsetFromOrigin());
}
void RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) override {
- int tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
+ automation_util::DispatchTreeDestroyedEventToAutomation(
render_frame_host->GetProcess()->GetID(),
- render_frame_host->GetRoutingID());
- AXTreeIDRegistry::GetInstance()->RemoveAXTreeID(tree_id);
- AutomationEventRouter::GetInstance()->DispatchTreeDestroyedEvent(
- tree_id,
+ render_frame_host->GetRoutingID(),
browser_context_);
}
private:
friend class content::WebContentsUserData<AutomationWebContentsObserver>;
- explicit AutomationWebContentsObserver(content::WebContents* web_contents)
+ AutomationWebContentsObserver(
+ content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
browser_context_(web_contents->GetBrowserContext()) {}
@@ -291,7 +245,6 @@ 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"));
@@ -303,7 +256,6 @@ AutomationInternalEnableTabFunction::Run() {
AutomationWebContentsObserver::CreateForWebContents(contents);
contents->EnableTreeOnlyAccessibilityMode();
-
int ax_tree_id = AXTreeIDRegistry::GetInstance()->GetOrCreateAXTreeID(
rfh->GetProcess()->GetID(), rfh->GetRoutingID());
@@ -318,12 +270,11 @@ 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
new file mode 100644
index 0000000..cbd2f9b
--- /dev/null
+++ b/chrome/browser/extensions/api/automation_internal/automation_util.cc
@@ -0,0 +1,213 @@
+// 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,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args) {
+ if (context && EventRouter::Get(context)) {
+ scoped_ptr<Event> event(
+ new Event(events::UNKNOWN, 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,
+ 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,
+ 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
new file mode 100644
index 0000000..218038b
--- /dev/null
+++ b/chrome/browser/extensions/api/automation_internal/automation_util.h
@@ -0,0 +1,41 @@
+// 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 8179d5b..581f90c 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(' +
- '$inputType, @input_type_+$inputType, @input_type_text)',
+ '$type, @input_type_+$type, @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 d1368d5..5bc04e7 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
@@ -8,9 +8,8 @@
#include "base/memory/singleton.h"
#include "chrome/browser/browser_process.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/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"
@@ -20,7 +19,6 @@
#include "ui/views/widget/widget.h"
using content::BrowserContext;
-using extensions::AutomationEventRouter;
// static
AutomationManagerAura* AutomationManagerAura::GetInstance() {
@@ -66,7 +64,20 @@ 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,
@@ -121,28 +132,21 @@ void AutomationManagerAura::ResetSerializer() {
void AutomationManagerAura::SendEvent(BrowserContext* context,
views::AXAuraObjWrapper* aura_obj,
ui::AXEvent event_type) {
- 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);
- }
+ 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());
}
void AutomationManagerAura::OnNativeFocusChanged(aura::Window* focused_now) {
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 77f2c89..9602ac5 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -138,6 +138,8 @@
'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 b49c3a5..51cdfe7 100644
--- a/chrome/common/extensions/api/automation.idl
+++ b/chrome/common/extensions/api/automation.idl
@@ -318,6 +318,8 @@
// 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;
@@ -368,21 +370,96 @@
// 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;
+ };
- //
- // Link attributes.
- //
+ // Attributes which are mixed in to an AutomationNode if it is a link.
+ dictionary LinkMixins {
+ // TODO(aboxhall): Add visited state
// The URL that this link will navigate to.
DOMString url;
+ };
- //
- // Document attributes.
- //
-
+ // Attributes which are mixed in to an AutomationNode if it is a document.
+ dictionary DocumentMixins {
// The URL of this document.
DOMString docUrl;
@@ -394,22 +471,23 @@
// The proportion (out of 1.0) that this doc has completed loading.
double docLoadingProgress;
+ };
- //
- // Scrollable container attributes.
- //
+ // TODO(aboxhall): document ScrollableMixins (e.g. what is scrollXMin? is it
+ // ever not 0?)
+ // 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;
+ };
- //
- // Editable text field attributes.
- //
-
+ // Attributes which are mixed in to an AutomationNode if it is editable text.
+ dictionary EditableTextMixins {
// The character index of the start of the selection within this editable
// text element; -1 if no selection.
long textSelStart;
@@ -420,11 +498,10 @@
// The input type, like email or number.
DOMString textInputType;
+ };
- //
- // Range attributes.
- //
-
+ // Attributes which are mixed in to an AutomationNode if it is a range.
+ dictionary RangeMixins {
// The current value for this range.
double valueForRange;
@@ -433,21 +510,21 @@
// The maximum possible value for this range.
double maxValueForRange;
+ };
- //
- // Table attributes.
- //
+ // TODO(aboxhall): live region mixins.
+ // 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;
+ };
- //
- // Table cell attributes.
- //
-
+ // Attributes which are mixed in to an AutomationNode if it is a table cell.
+ dictionary TableCellMixins {
// The zero-based index of the column that this cell is in.
long tableCellColumnIndex;
@@ -459,75 +536,6 @@
// 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 6f568cf..b290c63 100644
--- a/chrome/common/extensions/api/automation_internal.idl
+++ b/chrome/common/extensions/api/automation_internal.idl
@@ -6,10 +6,46 @@
// 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.
- [nocompile] dictionary AXEventParams {
+ dictionary AXEventParams {
// The tree id of the web contents that this update is for.
long treeID;
@@ -18,6 +54,10 @@ 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.
@@ -93,7 +133,5 @@ 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 632d07d..7f70eb5 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,9 +40,6 @@ v8::Local<v8::Object> ToEnumObject(v8::Isolate* isolate,
namespace extensions {
-TreeCache::TreeCache() {}
-TreeCache::~TreeCache() {}
-
class AutomationMessageFilter : public IPC::MessageFilter {
public:
explicit AutomationMessageFilter(AutomationInternalCustomBindings* owner)
@@ -50,7 +47,6 @@ class AutomationMessageFilter : public IPC::MessageFilter {
removed_(false) {
DCHECK(owner);
content::RenderThread::Get()->AddFilter(this);
- task_runner_ = base::ThreadTaskRunnerHandle::Get();
}
void Detach() {
@@ -60,15 +56,10 @@ class AutomationMessageFilter : public IPC::MessageFilter {
// IPC::MessageFilter
bool OnMessageReceived(const IPC::Message& message) override {
- 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;
+ if (owner_)
+ return owner_->OnMessageReceived(message);
+ else
+ return false;
}
void OnFilterRemoved() override {
@@ -76,11 +67,6 @@ class AutomationMessageFilter : public IPC::MessageFilter {
}
private:
- void OnMessageReceivedOnRenderThread(const IPC::Message& message) {
- if (owner_)
- owner_->OnMessageReceived(message);
- }
-
~AutomationMessageFilter() override {
Remove();
}
@@ -94,7 +80,6 @@ private:
AutomationInternalCustomBindings* owner_;
bool removed_;
- scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter);
};
@@ -104,46 +89,35 @@ 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.
- #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
+ 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);
}
AutomationInternalCustomBindings::~AutomationInternalCustomBindings() {
- if (message_filter_)
- message_filter_->Detach();
- STLDeleteContainerPairSecondPointers(tree_id_to_tree_cache_map_.begin(),
- tree_id_to_tree_cache_map_.end());
+ message_filter_->Detach();
}
-void AutomationInternalCustomBindings::OnMessageReceived(
+bool 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(
@@ -162,12 +136,6 @@ 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());
@@ -191,438 +159,9 @@ 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) {
- 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);
+ // TODO(dmazzoni): finish implementing this.
}
} // namespace extensions
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.h b/chrome/renderer/extensions/automation_internal_custom_bindings.h
index c4f2cd2..1bc8a79 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.h
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.h
@@ -6,7 +6,6 @@
#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"
@@ -18,27 +17,15 @@ 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,
- public ui::AXTreeDelegate {
+class AutomationInternalCustomBindings : public ObjectBackedNativeHandler {
public:
explicit AutomationInternalCustomBindings(ScriptContext* context);
~AutomationInternalCustomBindings() override;
- void OnMessageReceived(const IPC::Message& message);
+ bool OnMessageReceived(const IPC::Message& message);
private:
// Returns whether this extension has the "interact" permission set (either
@@ -52,120 +39,10 @@ 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 e5de712..5dc6b9b 100644
--- a/chrome/renderer/resources/extensions/automation/automation_node.js
+++ b/chrome/renderer/resources/extensions/automation/automation_node.js
@@ -8,124 +8,6 @@ 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();
@@ -138,12 +20,16 @@ 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 },
@@ -154,51 +40,16 @@ AutomationNodeImpl.prototype = {
},
get parent() {
- 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);
+ return this.hostTree || this.rootImpl.get(this.parentID);
},
get firstChild() {
- 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);
+ return this.childTree || this.rootImpl.get(this.childIds[0]);
},
get lastChild() {
- 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);
+ var childIds = this.childIds;
+ return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]);
},
get children() {
@@ -206,28 +57,24 @@ AutomationNodeImpl.prototype = {
return [this.childTree];
var children = [];
- 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);
+ for (var i = 0, childID; childID = this.childIds[i]; i++) {
+ logging.CHECK(this.rootImpl.get(childID));
+ children.push(this.rootImpl.get(childID));
}
return children;
},
get previousSibling() {
var parent = this.parent;
- var indexInParent = GetIndexInParent(this.treeID, this.id);
- if (parent && indexInParent > 0)
- return parent.children[indexInParent - 1];
+ if (parent && this.indexInParent > 0)
+ return parent.children[this.indexInParent - 1];
return undefined;
},
get nextSibling() {
var parent = this.parent;
- var indexInParent = GetIndexInParent(this.treeID, this.id);
- if (parent && indexInParent < parent.children.length)
- return parent.children[indexInParent + 1];
+ if (parent && this.indexInParent < parent.children.length)
+ return parent.children[this.indexInParent + 1];
return undefined;
},
@@ -323,20 +170,12 @@ 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=' + parentID +
- ' childIds=' + $JSON.stringify(childIDs);
+ ' parentID=' + impl.parentID +
+ ' childIds=' + $JSON.stringify(impl.childIds) +
+ ' attributes=' + $JSON.stringify(this.attributes);
},
dispatchEventAtCapturing_: function(event, path) {
@@ -380,9 +219,9 @@ AutomationNodeImpl.prototype = {
try {
listeners[i].callback(event);
} catch (e) {
- logging.WARNING('Error in event handler for ' + event.type +
- ' during phase ' + eventPhase + ': ' +
- e.message + '\nStack trace: ' + e.stack);
+ console.error('Error in event handler for ' + event.type +
+ 'during phase ' + eventPhase + ': ' +
+ e.message + '\nStack trace: ' + e.stack);
}
}
},
@@ -473,14 +312,17 @@ 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[attribute] !== attrValue)
+ if (this.attributesInternal[attribute] !== attrValue)
return false;
} else if (attrValue instanceof RegExp) {
- if (typeof this[attribute] != 'string')
+ if (typeof this.attributesInternal[attribute] != 'string')
return false;
- if (!attrValue.test(this[attribute]))
+ if (!attrValue.test(this.attributesInternal[attribute]))
return false;
} else {
// TODO(aboxhall): handle intlist case.
@@ -492,196 +334,171 @@ AutomationNodeImpl.prototype = {
}
};
-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;
- }
- });
-});
+// 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 }
+};
-floatAttributes.forEach(function (attributeName) {
- publicAttributes.push(attributeName);
- Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
- get: function() {
- return GetFloatAttribute(this.treeID, this.id, attributeName);
- }
- });
-});
-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);
- }
- });
-});
+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 TableCellMixinAttributes = {
+ tableCellColumnIndex: defaultIntAttribute(),
+ tableCellColumnSpan: defaultIntAttribute(1),
+ tableCellRowIndex: defaultIntAttribute(),
+ tableCellRowSpan: defaultIntAttribute(1)
+};
+
+var LiveRegionMixinAttributes = {
+ containerLiveAtomic: defaultBoolAttribute(),
+ containerLiveBusy: defaultBoolAttribute(),
+ containerLiveRelevant: defaultStringAttribute(),
+ containerLiveStatus: defaultStringAttribute(),
+};
/**
* AutomationRootNode.
@@ -706,88 +523,107 @@ 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;
- if (id == this.id)
- return this.wrapper;
+ return this.axNodeDataCache_[id];
+ },
- var obj = this.axNodeDataCache_[id];
- if (obj)
- return obj;
+ unserialize: function(update) {
+ var updateState = { pendingNodes: {}, newNodes: {} };
+ var oldRootId = this.id;
- obj = new AutomationNode(this);
- privates(obj).impl.treeID = this.treeID;
- privates(obj).impl.id = id;
- this.axNodeDataCache_[id] = 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;
+ }
+ }
- return obj;
- },
+ for (var i = 0; i < update.nodes.length; i++) {
+ if (!this.updateNode_(update.nodes[i], updateState))
+ return false;
+ }
- remove: function(id) {
- delete this.axNodeDataCache_[id];
+ 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;
+ }
+
+ // 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 true;
},
destroy: function() {
- this.dispatchEvent(schema.EventType.destroyed);
- },
+ if (this.hostTree)
+ this.hostTree.childTree = undefined;
+ this.hostTree = undefined;
- setHostNode(hostNode) {
- this.hostNode_ = hostNode;
+ this.dispatchEvent(schema.EventType.destroyed);
+ this.invalidate_(this.wrapper);
},
onAccessibilityEvent: function(eventParams) {
+ if (!this.unserialize(eventParams.update)) {
+ logging.WARNING('unserialization failed');
+ return false;
+ }
+
var targetNode = this.get(eventParams.targetID);
if (targetNode) {
var targetNodeImpl = privates(targetNode).impl;
@@ -815,8 +651,374 @@ 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',
@@ -831,8 +1033,7 @@ var AutomationNode = utils.expose('AutomationNode',
'removeEventListener',
'domQuerySelector',
'toString' ],
- readonly: publicAttributes.concat(
- ['parent',
+ readonly: ['parent',
'firstChild',
'lastChild',
'children',
@@ -842,24 +1043,13 @@ 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 59234f2..b2fc5c3b7 100644
--- a/chrome/renderer/resources/extensions/automation_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/automation_custom_bindings.js
@@ -16,11 +16,6 @@ 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();
/**
@@ -29,6 +24,7 @@ var schema = GetSchemaAdditions();
window.automationUtil = function() {};
// TODO(aboxhall): Look into using WeakMap
+var idToAutomationRootNode = {};
var idToCallback = {};
var DESKTOP_TREE_ID = 0;
@@ -37,7 +33,7 @@ automationUtil.storeTreeCallback = function(id, callback) {
if (!callback)
return;
- var targetTree = AutomationRootNode.get(id);
+ var targetTree = idToAutomationRootNode[id];
if (!targetTree) {
// If we haven't cached the tree, hold the callback until the tree is
// populated by the initial onAccessibilityEvent call.
@@ -62,7 +58,6 @@ 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
@@ -84,8 +79,8 @@ automation.registerCustomHook(function(bindingsAPI) {
var desktopTree = null;
apiFunctions.setHandleRequest('getDesktop', function(callback) {
- StartCachingAccessibilityTrees();
- desktopTree = AutomationRootNode.get(DESKTOP_TREE_ID);
+ desktopTree =
+ idToAutomationRootNode[DESKTOP_TREE_ID];
if (!desktopTree) {
if (DESKTOP_TREE_ID in idToCallback)
idToCallback[DESKTOP_TREE_ID].push(callback);
@@ -98,7 +93,8 @@ automation.registerCustomHook(function(bindingsAPI) {
// scope.
automationInternal.enableDesktop(routingID, function() {
if (lastError.hasError(chrome)) {
- AutomationRootNode.destroy(DESKTOP_TREE_ID);
+ delete idToAutomationRootNode[
+ DESKTOP_TREE_ID];
callback();
return;
}
@@ -129,65 +125,19 @@ 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 = AutomationRootNode.getOrCreate(id);
-
+ 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;
+ }
if (!privates(targetTree).impl.onAccessibilityEvent(data))
return;
@@ -199,7 +149,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.url &&
+ if (id != DESKTOP_TREE_ID && !targetTree.attributes.url &&
targetTree.children.length == 0) {
return;
}
@@ -208,6 +158,7 @@ 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);
}
@@ -215,17 +166,14 @@ automationInternal.onAccessibilityEvent.addListener(function(data) {
});
automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) {
- // Destroy the AutomationRootNode.
- var targetTree = AutomationRootNode.get(id);
+ var targetTree = idToAutomationRootNode[id];
if (targetTree) {
privates(targetTree).impl.destroy();
- AutomationRootNode.destroy(id);
+ delete idToAutomationRootNode[id];
} else {
logging.WARNING('no targetTree to destroy');
}
-
- // Destroy the native cache of the accessibility tree.
- DestroyAccessibilityTree(id);
+ delete idToAutomationRootNode[id];
});
exports.binding = automation.generate();
diff --git a/chrome/test/data/extensions/api_test/automation/sites/attributes.html b/chrome/test/data/extensions/api_test/automation/sites/mixins.html
index 5b8f63f..c27429d 100644
--- a/chrome/test/data/extensions/api_test/automation/sites/attributes.html
+++ b/chrome/test/data/extensions/api_test/automation/sites/mixins.html
@@ -5,12 +5,12 @@
-->
<html>
<head>
-<title>Automation Tests - Attributes</title>
+<title>Automation Tests - Mixin attributes</title>
</head>
<body>
- <!-- activedescendant attribute, owns default attribute-->
+ <!-- activedescendant mixin, owns default mixin-->
<input type="text" aria-activedescendant="opt6" aria-readonly="true"
- aria-autocomplete="list" role="combobox"
+ aria-owns="combobox-list" 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 attributes -->
+ <!-- link mixins -->
<a href="about://blank" id="real-link">Real link</a>
<div role="link" id="link-role">ARIA link</div>
- <!-- 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">
+ <!-- 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">
Textbox
</div>
@@ -37,22 +37,21 @@
document.querySelector('#text-input').setSelectionRange(2, 8);
</script>
- <!-- range attributes -->
- <input type="range" aria-label="range-input" max="5" value="4"></input>
+ <!-- range mixins -->
+ <input type="range" id="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"
- aria-label="slider-role"></div>
+ aria-valuenow="7" aria-valuetext="seven stars" id="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" aria-label="progressbar-role"></div>
+ aria-valuemax="1.0" id="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" aria-label="main">Content for scrollbar to control</div>
+ <div id="main">Content for scrollbar to control</div>
- <!-- table and cell attributes -->
+ <!-- table and cell mixins -->
<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/attributes.html b/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.html
index c2eba0b..f60d19c 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.html
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.html
@@ -4,4 +4,4 @@
* LICENSE file.
-->
<script src="common.js"></script>
-<script src="attributes.js"></script>
+<script src="mixins.js"></script>
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.js
index 0e0595e..b3ae61b 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/mixins.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 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 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 allTests = [
- function testDocumentAndScrollAttributes() {
- for (var i = 0; i < DocumentAttributes.length; i++) {
- var attribute = DocumentAttributes[i];
- assertTrue(attribute in rootNode,
- 'rootNode should have a ' + attribute + ' attribute');
+ function testDocumentAndScrollMixins() {
+ for (var i = 0; i < DocumentMixins.length; i++) {
+ var mixinAttribute = DocumentMixins[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');
+ for (var i = 0; i < ScrollableMixins.length; i++) {
+ var mixinAttribute = ScrollableMixins[i];
+ assertTrue(mixinAttribute in rootNode,
+ 'rootNode should have a ' + mixinAttribute + ' attribute');
}
assertEq(url, rootNode.docUrl);
- assertEq('Automation Tests - Attributes', rootNode.docTitle);
+ assertEq('Automation Tests - Mixin attributes', rootNode.docTitle);
assertEq(true, rootNode.docLoaded);
assertEq(1, rootNode.docLoadingProgress);
assertEq(0, rootNode.scrollX);
@@ -52,17 +52,22 @@ var allTests = [
chrome.test.succeed();
},
- function testActiveDescendant() {
+ function testActiveDescendantAndOwns() {
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 testLinkAttributes() {
+ function testLinkMixins() {
var links = rootNode.findAll({ role: 'link' });
assertEq(2, links.length);
@@ -73,56 +78,56 @@ var allTests = [
var ariaLink = links[1];
assertEq('ARIA link', ariaLink.name);
- assertTrue('url' in ariaLink, 'ariaLink should have an empty url');
- assertEq(undefined, ariaLink.url);
+ assertTrue('url' in ariaLink, 'ariaLink should have a url attribute');
+ assertEq('', ariaLink.url);
chrome.test.succeed();
},
- function testEditableTextAttributes() {
+ function testEditableTextMixins() {
var textFields = rootNode.findAll({ role: 'textField' });
assertEq(3, textFields.length);
- var EditableTextAttributes = [ 'textSelStart', 'textSelEnd' ];
+ var EditableTextMixins = [ 'textSelStart', 'textSelEnd' ];
for (var i = 0; i < textFields.length; i++) {
var textField = textFields[i];
- 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 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 input = textFields[0];
- assertEq('text-input', input.description);
+ assertEq('text-input', input.attributes.id);
assertEq(2, input.textSelStart);
assertEq(8, input.textSelEnd);
var textArea = textFields[1];
- 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('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(0, textArea.textSelStart);
assertEq(0, textArea.textSelEnd);
var ariaTextbox = textFields[2];
- assertEq('textbox-role', ariaTextbox.description);
+ assertEq('textbox-role', ariaTextbox.attributes.id);
assertEq(0, ariaTextbox.textSelStart);
assertEq(0, ariaTextbox.textSelEnd);
chrome.test.succeed();
},
- function testRangeAttributes() {
+ function testRangeMixins() {
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].description);
+ assertEq('progressbar-role', progressIndicators[0].attributes.id);
var scrollBars = rootNode.findAll({ role: 'scrollBar' });
assertEq(1, scrollBars.length);
@@ -131,22 +136,22 @@ var allTests = [
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
- 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');
+ 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');
}
}
var inputRange = sliders[0];
- assertEq('range-input', inputRange.description);
+ assertEq('range-input', inputRange.attributes.id);
assertEq(4, inputRange.valueForRange);
assertEq(0, inputRange.minValueForRange);
assertEq(5, inputRange.maxValueForRange);
var ariaSlider = sliders[1];
- assertEq('slider-role', ariaSlider.description);
+ assertEq('slider-role', ariaSlider.attributes.id);
assertEq(7, ariaSlider.valueForRange);
assertEq(1, ariaSlider.minValueForRange);
assertEq(10, ariaSlider.maxValueForRange);
@@ -167,7 +172,7 @@ var allTests = [
chrome.test.succeed();
},
- function testTableAttributes() {
+ function testTableMixins() {
var table = rootNode.find({ role: 'table' });;
assertEq(3, table.tableRowCount);
assertEq(3, table.tableColumnCount);
@@ -220,22 +225,22 @@ var allTests = [
chrome.test.succeed();
},
- function testNoAttributes() {
- var div = rootNode.find({ attributes: { description: 'main' } });
+ function testNoMixins() {
+ var div = rootNode.find({ attributes: { id: 'main' } });
assertTrue(div !== undefined);
- var allAttributes = [].concat(ActiveDescendantAttribute,
- LinkAttributes,
- DocumentAttributes,
- ScrollableAttributes,
- EditableTextAttributes,
- RangeAttributes,
- TableAttributes,
- TableCellAttributes);
- for (var attributeAttr in allAttributes) {
- assertFalse(attributeAttr in div);
+ var allMixins = [].concat(ActiveDescendantMixin,
+ LinkMixins,
+ DocumentMixins,
+ ScrollableMixins,
+ EditableTextMixins,
+ RangeMixins,
+ TableMixins,
+ TableCellMixins);
+ for (var mixinAttr in allMixins) {
+ assertFalse(mixinAttr in div);
}
chrome.test.succeed();
}
];
-setUpAndRunTests(allTests, 'attributes.html');
+setUpAndRunTests(allTests, 'mixins.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 c45bd6e..64c4e36 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,47 +5,48 @@
// 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) {
- var result = JSON.parse(JSON.stringify(state));
- delete result[StateType.horizontal];
- delete result[StateType.hovered];
- delete result[StateType.vertical];
- return result;
+ delete state[StateType.horizontal];
+ delete state[StateType.hovered];
+ delete state[StateType.vertical];
};
var allTests = [
function testSimplePage() {
var title = rootNode.docTitle;
assertEq('Automation Tests', title);
-
- var state = RemoveUntestedStates(rootNode.state);
+ RemoveUntestedStates(rootNode.state);
assertEq(
- {enabled: true, focusable: true, readOnly: true},
- state);
-
+ {enabled: true, focusable: true, readOnly: true},
+ rootNode.state);
var children = rootNode.children;
assertEq(RoleType.rootWebArea, rootNode.role);
assertEq(1, children.length);
var body = children[0];
assertEq('body', body.htmlTag);
- state = RemoveUntestedStates(body.state);
- assertEq({enabled: true, readOnly: true}, state);
+
+ RemoveUntestedStates(body.state);
+ assertEq({enabled: true, readOnly: true},
+ body.state);
var contentChildren = body.children;
assertEq(3, contentChildren.length);
var okButton = contentChildren[0];
assertEq('Ok', okButton.name);
- state = RemoveUntestedStates(okButton.state);
- assertEq({enabled: true, focusable: true, readOnly: true}, state);
+ RemoveUntestedStates(okButton.state);
+ assertEq({enabled: true, focusable: true, readOnly: true},
+ okButton.state);
var userNameInput = contentChildren[1];
assertEq('Username',
userNameInput.description);
- state = RemoveUntestedStates(userNameInput.state);
- assertEq({enabled: true, focusable: true}, state);
+ RemoveUntestedStates(userNameInput.state);
+ assertEq({enabled: true, focusable: true},
+ userNameInput.state);
var cancelButton = contentChildren[2];
assertEq('Cancel',
cancelButton.name);
- state = RemoveUntestedStates(cancelButton.state);
- assertEq({enabled: true, focusable: true, readOnly: true}, state);
+ RemoveUntestedStates(cancelButton.state);
+ assertEq({enabled: true, focusable: true, readOnly: true},
+ cancelButton.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 19a1416..dcb1861b 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 == "subtreeCreated" && change.target.name == "New") {
+ if (change.type == "nodeCreated" && 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
new file mode 100644
index 0000000..867927b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/automation/tests/unit/manifest.json
@@ -0,0 +1,8 @@
+{
+ "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
new file mode 100644
index 0000000..cc788ec
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/automation/tests/unit/test.js
@@ -0,0 +1,508 @@
+// 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
new file mode 100644
index 0000000..77bad90
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/automation/tests/unit/unit.html
@@ -0,0 +1,6 @@
+<!--
+ * 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 c9e6d83..d30c258 100644
--- a/content/browser/accessibility/browser_accessibility_manager_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_unittest.cc
@@ -90,15 +90,13 @@ class TestBrowserAccessibilityDelegate
return gfx::kNullAcceleratedWidget;
}
gfx::NativeViewAccessible AccessibilityGetNativeViewAccessible() override {
- return nullptr;
+ return NULL;
}
BrowserAccessibilityManager* AccessibilityGetChildFrame(
int accessibility_node_id) override {
- return nullptr;
- }
- BrowserAccessibility* AccessibilityGetParentFrame() override {
- return nullptr;
+ return NULL;
}
+ BrowserAccessibility* AccessibilityGetParentFrame() override { return NULL; }
void AccessibilityGetAllChildFrames(
std::vector<BrowserAccessibilityManager*>* child_frames) override {}
@@ -143,7 +141,7 @@ TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
BrowserAccessibilityManager* manager =
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, button, checkbox),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
@@ -157,7 +155,7 @@ TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
manager =
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, button, checkbox),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
@@ -248,7 +246,7 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) {
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(tree1_root,
tree1_child1, tree1_child2, tree1_child3),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
@@ -424,7 +422,7 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) {
tree1_child1, tree1_grandchild1,
tree1_child2, tree1_grandchild2,
tree1_child3, tree1_grandchild3),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
@@ -552,7 +550,7 @@ TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) {
BrowserAccessibilityManager* manager =
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(tree1_1, tree1_2, tree1_3, tree1_4),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory());
ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
@@ -686,7 +684,7 @@ TEST(BrowserAccessibilityManagerTest, BoundsForRange) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -774,7 +772,7 @@ TEST(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -834,7 +832,7 @@ TEST(BrowserAccessibilityManagerTest, BoundsForRangeScrolledWindow) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, static_text, inline_text),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -920,7 +918,7 @@ TEST(BrowserAccessibilityManagerTest, MAYBE_BoundsForRangeOnParentElement) {
MakeAXTreeUpdate(
root, div, static_text1, img,
static_text2, inline_text1, inline_text2),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -967,7 +965,7 @@ TEST(BrowserAccessibilityManagerTest, NextPreviousInTreeOrder) {
scoped_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, node2, node3, node4, node5),
- nullptr,
+ NULL,
new CountedBrowserAccessibilityFactory()));
BrowserAccessibility* root_accessible = manager->GetRoot();
@@ -977,69 +975,18 @@ TEST(BrowserAccessibilityManagerTest, NextPreviousInTreeOrder) {
node3_accessible->PlatformGetChild(0);
BrowserAccessibility* node5_accessible = root_accessible->PlatformGetChild(2);
- ASSERT_EQ(nullptr, manager->NextInTreeOrder(nullptr));
+ ASSERT_EQ(NULL, manager->NextInTreeOrder(NULL));
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(nullptr, manager->NextInTreeOrder(node5_accessible));
+ ASSERT_EQ(NULL, manager->NextInTreeOrder(node5_accessible));
- ASSERT_EQ(nullptr, manager->PreviousInTreeOrder(nullptr));
+ ASSERT_EQ(NULL, manager->PreviousInTreeOrder(NULL));
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 86fbc9c..8a913a1 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,7 +72,31 @@ void LoggingNativeHandler::ParseArgs(
*error_message = "Error: " + std::string(*v8::String::Utf8Value(args[1]));
}
- *error_message += "\n" + context()->GetStackTraceAsString();
+ 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;
}
} // namespace extensions
diff --git a/extensions/renderer/logging_native_handler.h b/extensions/renderer/logging_native_handler.h
index ca9938c..e9f4f20 100644
--- a/extensions/renderer/logging_native_handler.h
+++ b/extensions/renderer/logging_native_handler.h
@@ -46,6 +46,9 @@ 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 9321468..58f1b4c 100644
--- a/extensions/renderer/script_context.cc
+++ b/extensions/renderer/script_context.cc
@@ -58,15 +58,6 @@ 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.
@@ -382,27 +373,6 @@ 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 4a9531a..b6e49b9 100644
--- a/extensions/renderer/script_context.h
+++ b/extensions/renderer/script_context.h
@@ -178,9 +178,6 @@ 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 60fa888..d07f239 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -204,12 +204,9 @@ 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)) {
- // Make sure root_ always points to something valid, even inside
- // DestroySubtree.
- AXNode* old_root = root_;
+ if (root_)
+ DestroySubtree(root_, update_state);
root_ = node;
- if (old_root)
- DestroySubtree(old_root, update_state);
}
return success;
@@ -224,11 +221,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 77ef8cd..40aeeff 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(1, fake_delegate.deleted_ids()[0]);
- EXPECT_EQ(2, fake_delegate.deleted_ids()[1]);
+ EXPECT_EQ(2, fake_delegate.deleted_ids()[0]);
+ EXPECT_EQ(1, fake_delegate.deleted_ids()[1]);
ASSERT_EQ(1U, fake_delegate.subtree_deleted_ids().size());
EXPECT_EQ(1, fake_delegate.subtree_deleted_ids()[0]);