diff options
author | aboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-10 18:37:14 +0000 |
---|---|---|
committer | aboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-10 18:37:14 +0000 |
commit | ed51006db1465dc8542bb6508514fd89414f3f3b (patch) | |
tree | 6972994063c5793502e7a96882360d4dda516c87 | |
parent | 09f60dd57009ea1ac711bf6592e9d90fe66a9e78 (diff) | |
download | chromium_src-ed51006db1465dc8542bb6508514fd89414f3f3b.zip chromium_src-ed51006db1465dc8542bb6508514fd89414f3f3b.tar.gz chromium_src-ed51006db1465dc8542bb6508514fd89414f3f3b.tar.bz2 |
This replaces AutomationTree with AutomationRootNode, which is a regular node which happens to be the root of the tree as well, and contains the lookup table for all other nodes etc.
BUG=309681
TBR=jam@chromium.org
NOTRY=true
Review URL: https://codereview.chromium.org/311913002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276096 0039d316-1c4b-4281-b951-d872f2087c98
21 files changed, 450 insertions, 301 deletions
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 7ac3fa1..88188d7 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1916,6 +1916,7 @@ 'renderer/extensions/module_system_unittest.cc', 'renderer/extensions/renderer_permissions_policy_delegate_unittest.cc', 'renderer/extensions/safe_builtins_unittest.cc', + 'renderer/extensions/utils_unittest.cc', 'renderer/media/cast_ipc_dispatcher_unittest.cc', 'renderer/media/chrome_webrtc_log_message_delegate_unittest.cc', 'renderer/net/net_error_helper_core_unittest.cc', diff --git a/chrome/common/extensions/api/automation.idl b/chrome/common/extensions/api/automation.idl index 11de2d1..6c9e8f8 100644 --- a/chrome/common/extensions/api/automation.idl +++ b/chrome/common/extensions/api/automation.idl @@ -31,8 +31,14 @@ // A listener for events on an <code>AutomationNode</code>. callback AutomationListener = void(AutomationEvent event); - // A single node in an <code>AutomationTree</code>. + // A single node in an Automation tree. [nocompile] dictionary AutomationNode { + // The root node of the tree containing this AutomationNode. + AutomationRootNode root; + + // Whether this AutomationNode is an AutomationRootNode. + bool isRootNode; + // Unique ID to identify this node. long id; @@ -75,18 +81,34 @@ DOMString eventType, AutomationListener listener, bool capture); }; - // The automation tree for a single page. - [nocompile] dictionary AutomationTree { - AutomationNode root; - - static void addEventListener( - DOMString eventType, AutomationListener listener, bool capture); - static void removeEventListener( - DOMString eventType, AutomationListener listener, bool capture); + // The root node of the automation tree for a single frame or desktop. + // This may be: + // <ul> + // <li> The desktop + // <li> The top frame of a page + // <li> A frame or iframe within a page + // </ul> + // Thus, an <code>AutomationRootNode</code> may be a descendant of one or + // more <code>AutomationRootNode</code>s, and in turn have one or more + // <code>AutomationRootNode</code>s in its descendants. Thus, the + // <code>root</code> property of the <code>AutomationRootNode</code> will be + // the immediate parent <code>AutomationRootNode</code>, or <code>null</code> + // if this is the top-level <code>AutomationRootNode</code>. + // + // Extends $(ref:automation.AutomationNode). + [nocompile] dictionary AutomationRootNode { + // Whether this AutomationRootNode is loaded or not. If false, call load() + // to get the contents. + // TODO(aboxhall/dtseng): implement. + bool loaded; + + // Load the accessibility tree for this AutomationRootNode. + // TODO(aboxhall/dtseng): implement. + static void load(RootCallback callback); }; - // Called when the <code>AutomationTree</code> for the page is available. - callback RootCallback = void(AutomationTree tree); + // Called when the <code>AutomationRootNode</code> for the page is available. + callback RootCallback = void(AutomationRootNode rootNode); interface Functions { // Get the automation tree for the tab with the given tabId, or the current diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc index c8e411e..04db8f1 100644 --- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc +++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc @@ -160,7 +160,6 @@ void ChromeExtensionsDispatcherDelegate::PopulateSourceMap( source_map->RegisterSource("automation", IDR_AUTOMATION_CUSTOM_BINDINGS_JS); source_map->RegisterSource("automationEvent", IDR_AUTOMATION_EVENT_JS); source_map->RegisterSource("automationNode", IDR_AUTOMATION_NODE_JS); - source_map->RegisterSource("automationTree", IDR_AUTOMATION_TREE_JS); source_map->RegisterSource("browserAction", IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS); source_map->RegisterSource("declarativeContent", diff --git a/chrome/renderer/extensions/utils_unittest.cc b/chrome/renderer/extensions/utils_unittest.cc new file mode 100644 index 0000000..3a718dd --- /dev/null +++ b/chrome/renderer/extensions/utils_unittest.cc @@ -0,0 +1,123 @@ +// 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 "base/strings/stringprintf.h" +#include "chrome/test/base/module_system_test.h" +#include "grit/extensions_renderer_resources.h" + +namespace extensions { +namespace { + +class UtilsUnittest : public ModuleSystemTest { + protected: + void RegisterTestModule(const char* code) { + RegisterModule("test", + base::StringPrintf( + "var assert = requireNative('assert');\n" + "var AssertTrue = assert.AssertTrue;\n" + "var AssertFalse = assert.AssertFalse;\n" + "var utils = require('utils');\n" + "%s", + code)); + } + + private: + virtual void SetUp() OVERRIDE { + ModuleSystemTest::SetUp(); + + RegisterModule("utils", IDR_UTILS_JS); + OverrideNativeHandler("schema_registry", + "exports.GetSchema = function() {};"); + OverrideNativeHandler("logging", + "exports.CHECK = function() {};\n" + "exports.WARNING = function() {};"); + } +}; + +TEST_F(UtilsUnittest, TestNothing) { + ExpectNoAssertionsMade(); +} + +TEST_F(UtilsUnittest, SuperClass) { + ModuleSystem::NativesEnabledScope natives_enabled_scope( + context_->module_system()); + RegisterTestModule( + "function SuperClassImpl() {}\n" + "\n" + "SuperClassImpl.prototype = {\n" + " attrA: 'aSuper',\n" + " attrB: 'bSuper',\n" + " func: function() { return 'func'; },\n" + " superFunc: function() { return 'superFunc'; }\n" + "};\n" + "\n" + "function SubClassImpl() {\n" + " SuperClassImpl.call(this);\n" + "}\n" + "\n" + "SubClassImpl.prototype = {\n" + " __proto__: SuperClassImpl.prototype,\n" + " attrA: 'aSub',\n" + " attrC: 'cSub',\n" + " func: function() { return 'overridden'; },\n" + " subFunc: function() { return 'subFunc'; }\n" + "};\n" + "\n" + "var SuperClass = utils.expose('SuperClass',\n" + " SuperClassImpl,\n" + " { functions: ['func', 'superFunc'],\n" + " properties: ['attrA', 'attrB'] });\n" + "\n" + "var SubClass = utils.expose('SubClass',\n" + " SubClassImpl,\n" + " { superclass: SuperClass,\n" + " functions: ['subFunc'],\n" + " properties: ['attrC'] });\n" + "\n" + "var supe = new SuperClass();\n" + "AssertTrue(supe.attrA == 'aSuper');\n" + "AssertTrue(supe.attrB == 'bSuper');\n" + "AssertFalse('attrC' in supe);\n" + "AssertTrue(supe.func() == 'func');\n" + "AssertTrue('superFunc' in supe);\n" + "AssertTrue(supe.superFunc() == 'superFunc');\n" + "AssertFalse('subFunc' in supe);\n" + "AssertTrue(supe instanceof SuperClass);\n" + "\n" + "var sub = new SubClass();\n" + "AssertTrue(sub.attrA == 'aSub');\n" + "AssertTrue(sub.attrB == 'bSuper');\n" + "AssertTrue(sub.attrC == 'cSub');\n" + "AssertTrue(sub.func() == 'overridden');\n" + "AssertTrue(sub.superFunc() == 'superFunc');\n" + "AssertTrue('subFunc' in sub);\n" + "AssertTrue(sub.subFunc() == 'subFunc');\n" + "AssertTrue(sub instanceof SuperClass);\n" + "AssertTrue(sub instanceof SubClass);\n" + "\n" + "function SubSubClassImpl() {}\n" + "SubSubClassImpl.prototype = Object.create(SubClassImpl.prototype);\n" + "SubSubClassImpl.prototype.subSubFunc = function() { return 'subsub'; }\n" + "\n" + "var SubSubClass = utils.expose('SubSubClass',\n" + " SubSubClassImpl,\n" + " { superclass: SubClass,\n" + " functions: ['subSubFunc'] });\n" + "var subsub = new SubSubClass();\n" + "AssertTrue(subsub.attrA == 'aSub');\n" + "AssertTrue(subsub.attrB == 'bSuper');\n" + "AssertTrue(subsub.attrC == 'cSub');\n" + "AssertTrue(subsub.func() == 'overridden');\n" + "AssertTrue(subsub.superFunc() == 'superFunc');\n" + "AssertTrue(subsub.subFunc() == 'subFunc');\n" + "AssertTrue(subsub.subSubFunc() == 'subsub');\n" + "AssertTrue(subsub instanceof SuperClass);\n" + "AssertTrue(subsub instanceof SubClass);\n" + "AssertTrue(subsub instanceof SubSubClass);\n"); + + context_->module_system()->Require("test"); +} + +} // namespace +} // namespace extensions diff --git a/chrome/renderer/resources/extensions/automation/automation_node.js b/chrome/renderer/resources/extensions/automation/automation_node.js index 944afc8..838de6e 100644 --- a/chrome/renderer/resources/extensions/automation/automation_node.js +++ b/chrome/renderer/resources/extensions/automation/automation_node.js @@ -11,42 +11,46 @@ var IsInteractPermitted = /** * A single node in the Automation tree. - * @param {AutomationTree} owner The owning tree. + * @param {AutomationRootNodeImpl} root The root of the tree. * @constructor */ -var AutomationNodeImpl = function(owner) { - this.owner = owner; +function AutomationNodeImpl(root) { + this.rootImpl = root; this.childIds = []; this.attributes = {}; this.listeners = {}; this.location = { left: 0, top: 0, width: 0, height: 0 }; -}; +} AutomationNodeImpl.prototype = { id: -1, role: '', - loaded: false, state: { busy: true }, + isRootNode: false, + + get root() { + return this.rootImpl.wrapper; + }, parent: function() { - return this.owner.get(this.parentID); + return this.rootImpl.get(this.parentID); }, firstChild: function() { - var node = this.owner.get(this.childIds[0]); + var node = this.rootImpl.get(this.childIds[0]); return node; }, lastChild: function() { var childIds = this.childIds; - var node = this.owner.get(childIds[childIds.length - 1]); + var node = this.rootImpl.get(childIds[childIds.length - 1]); return node; }, children: function() { var children = []; for (var i = 0, childID; childID = this.childIds[i]; i++) - children.push(this.owner.get(childID)); + children.push(this.rootImpl.get(childID)); return children; }, @@ -86,7 +90,7 @@ AutomationNodeImpl.prototype = { this.removeEventListener(eventType, callback); if (!this.listeners[eventType]) this.listeners[eventType] = []; - this.listeners[eventType].push({callback: callback, capture: capture}); + this.listeners[eventType].push({callback: callback, capture: !!capture}); }, // TODO(dtseng/aboxhall): Check this impl against spec. @@ -108,7 +112,6 @@ AutomationNodeImpl.prototype = { // TODO(aboxhall/dtseng): handle unloaded parent node parent = parent.parent(); } - path.push(this.owner.wrapper); var event = new AutomationEvent(eventType, this.wrapper); // Dispatch the event through the propagation path in three phases: @@ -162,7 +165,6 @@ AutomationNodeImpl.prototype = { var listeners = nodeImpl.listeners[event.type]; if (!listeners) return; - var eventPhase = event.eventPhase; for (var i = 0; i < listeners.length; i++) { if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture) @@ -182,8 +184,8 @@ AutomationNodeImpl.prototype = { performAction_: function(actionType, opt_args) { // Not yet initialized. - if (this.owner.processID === undefined || - this.owner.routingID === undefined || + if (this.rootImpl.processID === undefined || + this.rootImpl.routingID === undefined || this.wrapper.id === undefined) { return; } @@ -194,14 +196,179 @@ AutomationNodeImpl.prototype = { ' {"interact": true} in the "automation" manifest key.'); } - automationInternal.performAction({ processID: this.owner.processID, - routingID: this.owner.routingID, + automationInternal.performAction({ processID: this.rootImpl.processID, + routingID: this.rootImpl.routingID, automationNodeID: this.wrapper.id, actionType: actionType }, opt_args || {}); } }; +// Maps an attribute to its default value in an invalidated node. +// These attributes are taken directly from the Automation idl. +var AutomationAttributeDefaults = { + 'id': -1, + 'role': '', + 'state': {}, + 'location': { left: 0, top: 0, width: 0, height: 0 } +}; + + +var AutomationAttributeTypes = [ + 'boolAttributes', + 'floatAttributes', + 'htmlAttributes', + 'intAttributes', + 'intlistAttributes', + 'stringAttributes' +]; + + +/** + * AutomationRootNode. + * + * An AutomationRootNode is the javascript end of an AXTree living in the + * browser. AutomationRootNode handles unserializing incremental updates from + * the source AXTree. Each update contains node data that form a complete tree + * after applying the update. + * + * A brief note about ids used through this class. The source AXTree assigns + * unique ids per node and we use these ids to build a hash to the actual + * AutomationNode object. + * Thus, tree traversals amount to a lookup in our hash. + * + * The tree itself is identified by the process id and routing id of the + * renderer widget host. + * @constructor + */ +function AutomationRootNodeImpl(processID, routingID) { + AutomationNodeImpl.call(this, this); + this.processID = processID; + this.routingID = routingID; + this.axNodeDataCache_ = {}; +} + +AutomationRootNodeImpl.prototype = { + __proto__: AutomationNodeImpl.prototype, + + isRootNode: true, + + get: function(id) { + return this.axNodeDataCache_[id]; + }, + + invalidate: function(node) { + if (!node) + return; + + var children = node.children(); + + for (var i = 0, child; child = children[i]; i++) + 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 = node.id; + for (var key in AutomationAttributeDefaults) { + nodeImpl[key] = AutomationAttributeDefaults[key]; + } + nodeImpl.loaded = false; + nodeImpl.id = id; + this.axNodeDataCache_[id] = undefined; + }, + + update: function(data) { + var didUpdateRoot = false; + + if (data.nodes.length == 0) + return false; + + for (var i = 0; i < data.nodes.length; i++) { + var nodeData = data.nodes[i]; + var node = this.axNodeDataCache_[nodeData.id]; + if (!node) { + if (nodeData.role == 'rootWebArea' || nodeData.role == 'desktop') { + // |this| is an AutomationRootNodeImpl; retrieve the + // AutomationRootNode instance instead. + node = this.wrapper; + didUpdateRoot = true; + } else { + node = new AutomationNode(this); + } + } + var nodeImpl = privates(node).impl; + + // Update children. + var oldChildIDs = nodeImpl.childIds; + var newChildIDs = nodeData.childIds || []; + var newChildIDsHash = {}; + + for (var j = 0, newId; newId = newChildIDs[j]; j++) { + // Hash the new child ids for faster lookup. + newChildIDsHash[newId] = newId; + + // We need to update all new children's parent id regardless. + var childNode = this.get(newId); + if (!childNode) { + childNode = new AutomationNode(this); + this.axNodeDataCache_[newId] = childNode; + privates(childNode).impl.id = newId; + } + privates(childNode).impl.indexInParent = j; + privates(childNode).impl.parentID = nodeData.id; + } + + for (var k = 0, oldId; oldId = oldChildIDs[k]; k++) { + // However, we must invalidate all old child ids that are no longer + // children. + if (!newChildIDsHash[oldId]) { + this.invalidate(this.get(oldId)); + } + } + + for (var key in AutomationAttributeDefaults) { + if (key in nodeData) + nodeImpl[key] = nodeData[key]; + else + nodeImpl[key] = AutomationAttributeDefaults[key]; + } + for (var attributeTypeIndex = 0; + attributeTypeIndex < AutomationAttributeTypes.length; + attributeTypeIndex++) { + var attributeType = AutomationAttributeTypes[attributeTypeIndex]; + for (var attributeID in nodeData[attributeType]) { + nodeImpl.attributes[attributeID] = + nodeData[attributeType][attributeID]; + } + } + nodeImpl.childIds = newChildIDs; + nodeImpl.loaded = true; + this.axNodeDataCache_[node.id] = node; + } + var node = this.get(data.targetID); + if (node) + nodeImpl.dispatchEvent(data.eventType); + return true; + }, + + toString: function() { + function toStringInternal(node, indent) { + if (!node) + return ''; + var output = + new Array(indent).join(' ') + privates(node).impl.toString() + '\n'; + indent += 2; + for (var i = 0; i < node.children().length; i++) + output += toStringInternal(node.children()[i], indent); + return output; + } + return toStringInternal(this, 0); + } +}; + + var AutomationNode = utils.expose('AutomationNode', AutomationNodeImpl, { functions: ['parent', @@ -216,11 +383,18 @@ var AutomationNode = utils.expose('AutomationNode', 'setSelection', 'addEventListener', 'removeEventListener'], - readonly: ['id', + readonly: ['isRootNode', + 'id', 'role', 'state', 'location', - 'attributes', - 'loaded'] }); + 'attributes'] }); + +var AutomationRootNode = utils.expose('AutomationRootNode', + AutomationRootNodeImpl, + { superclass: AutomationNode, + functions: ['load'], + readonly: ['loaded'] }); exports.AutomationNode = AutomationNode; +exports.AutomationRootNode = AutomationRootNode; diff --git a/chrome/renderer/resources/extensions/automation/automation_tree.js b/chrome/renderer/resources/extensions/automation/automation_tree.js deleted file mode 100644 index e7afd4b..0000000 --- a/chrome/renderer/resources/extensions/automation/automation_tree.js +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -var Event = require('event_bindings').Event; -var AutomationNode = require('automationNode').AutomationNode; -var utils = require('utils'); - -// Maps an attribute to its default value in an invalidated node. -// These attributes are taken directly from the Automation idl. -var AutomationAttributeDefaults = { - 'id': -1, - 'role': '', - 'state': {}, - 'location': { left: 0, top: 0, width: 0, height: 0 } -}; - - -var AutomationAttributeTypes = [ - 'boolAttributes', - 'floatAttributes', - 'htmlAttributes', - 'intAttributes', - 'intlistAttributes', - 'stringAttributes' -]; - - -/** - * AutomationTree. - * - * An AutomationTree is the javascript end of an AXTree living in the browser. - * AutomationTree handles unserializing incremental updates from the source - * AXTree. Each update contains node data that form a complete tree after - * applying the update. - * - * A brief note about ids used through this class. The source AXTree assigns - * unique ids per node and we use these ids to build a hash to the actual - * AutomationNode object. - * Thus, tree traversals amount to a lookup in our hash. - * - * The tree itself is identified by the process id and routing id of the - * renderer widget host. - * @constructor - */ -var AutomationTreeImpl = function(processID, routingID) { - this.processID = processID; - this.routingID = routingID; - this.listeners = {}; - this.root = new AutomationNode(this); - this.axNodeDataCache_ = {}; -}; - -AutomationTreeImpl.prototype = { - root: null, - - get: function(id) { - return this.axNodeDataCache_[id]; - }, - - invalidate: function(node) { - if (!node) - return; - - var children = node.children(); - - for (var i = 0, child; child = children[i]; i++) - this.invalidate(child); - - var id = node.id; - for (var key in AutomationAttributeDefaults) { - privates(node).impl[key] = AutomationAttributeDefaults[key]; - } - privates(node).impl.loaded = false; - privates(node).impl.id = id; - this.axNodeDataCache_[id] = undefined; - }, - - // TODO(aboxhall): make AutomationTree inherit from AutomationNode (or a - // mutual ancestor) and remove this code. - addEventListener: function(eventType, callback, capture) { - this.removeEventListener(eventType, callback); - if (!this.listeners[eventType]) - this.listeners[eventType] = []; - this.listeners[eventType].push({callback: callback, capture: capture}); - }, - - removeEventListener: function(eventType, callback) { - if (this.listeners[eventType]) { - var listeners = this.listeners[eventType]; - for (var i = 0; i < listeners.length; i++) { - if (callback === listeners[i].callback) - listeners.splice(i, 1); - } - } - }, - - update: function(data) { - var didUpdateRoot = false; - - if (data.nodes.length == 0) - return false; - - for (var i = 0; i < data.nodes.length; i++) { - var nodeData = data.nodes[i]; - var node = this.axNodeDataCache_[nodeData.id]; - if (!node) { - node = new AutomationNode(this); - } - - // Update children. - var oldChildIDs = privates(node).impl.childIds; - var newChildIDs = nodeData.childIds || []; - var newChildIDsHash = {}; - - for (var j = 0, newId; newId = newChildIDs[j]; j++) { - // Hash the new child ids for faster lookup. - newChildIDsHash[newId] = newId; - - // We need to update all new children's parent id regardless. - var childNode = this.get(newId); - if (!childNode) { - childNode = new AutomationNode(this); - this.axNodeDataCache_[newId] = childNode; - privates(childNode).impl.id = newId; - } - privates(childNode).impl.indexInParent = j; - privates(childNode).impl.parentID = nodeData.id; - } - - for (var k = 0, oldId; oldId = oldChildIDs[k]; k++) { - // However, we must invalidate all old child ids that are no longer - // children. - if (!newChildIDsHash[oldId]) { - this.invalidate(this.get(oldId)); - } - } - - if (nodeData.role == 'rootWebArea' || nodeData.role == 'desktop') { - this.root = node; - didUpdateRoot = true; - } - for (var key in AutomationAttributeDefaults) { - if (key in nodeData) - privates(node).impl[key] = nodeData[key]; - else - privates(node).impl[key] = AutomationAttributeDefaults[key]; - } - for (var attributeTypeIndex = 0; - attributeTypeIndex < AutomationAttributeTypes.length; - attributeTypeIndex++) { - var attributeType = AutomationAttributeTypes[attributeTypeIndex]; - for (var attributeID in nodeData[attributeType]) { - privates(node).impl.attributes[attributeID] = - nodeData[attributeType][attributeID]; - } - } - privates(node).impl.childIds = newChildIDs; - privates(node).impl.loaded = true; - this.axNodeDataCache_[node.id] = node; - } - var node = this.get(data.targetID); - if (node) - privates(node).impl.dispatchEvent(data.eventType); - return true; - }, - - toString: function() { - function toStringInternal(node, indent) { - if (!node) - return ''; - var output = - new Array(indent).join(' ') + privates(node).impl.toString() + '\n'; - indent += 2; - for (var i = 0; i < node.children().length; i++) - output += toStringInternal(node.children()[i], indent); - return output; - } - return toStringInternal(this.root, 0); - } -}; - -exports.AutomationTree = utils.expose('AutomationTree', - AutomationTreeImpl, - { functions: ['addEventListener', - 'removeEventListener'], - readonly: ['root'] }); diff --git a/chrome/renderer/resources/extensions/automation_custom_bindings.js b/chrome/renderer/resources/extensions/automation_custom_bindings.js index 96e9e8d..8a0fe48 100644 --- a/chrome/renderer/resources/extensions/automation_custom_bindings.js +++ b/chrome/renderer/resources/extensions/automation_custom_bindings.js @@ -4,7 +4,7 @@ // Custom bindings for the automation API. var AutomationNode = require('automationNode').AutomationNode; -var AutomationTree = require('automationTree').AutomationTree; +var AutomationRootNode = require('automationNode').AutomationRootNode; var automation = require('binding').Binding.create('automation'); var automationInternal = require('binding').Binding.create('automationInternal').generate(); @@ -16,20 +16,20 @@ var schema = requireNative('automationInternal').GetSchemaAdditions(); // TODO(aboxhall): Look into using WeakMap -var idToAutomationTree = {}; +var idToAutomationRootNode = {}; var idToCallback = {}; // TODO(dtseng): Move out to automation/automation_util.js or as a static member -// of AutomationTree to keep this file clean. +// of AutomationRootNode to keep this file clean. /* - * Creates an id associated with a particular AutomationTree based upon a + * Creates an id associated with a particular AutomationRootNode based upon a * renderer/renderer host pair's process and routing id. */ -var createAutomationTreeID = function(pid, rid) { +var createAutomationRootNodeID = function(pid, rid) { return pid + '_' + rid; }; -var DESKTOP_TREE_ID = createAutomationTreeID(0, 0); +var DESKTOP_TREE_ID = createAutomationRootNodeID(0, 0); automation.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; @@ -38,7 +38,7 @@ automation.registerCustomHook(function(bindingsAPI) { apiFunctions.setHandleRequest('getTree', function getTree(tabId, callback) { // enableTab() ensures the renderer for the active or specified tab has // accessibility enabled, and fetches its process and routing ids to use as - // a key in the idToAutomationTree map. The callback to enableActiveTab is + // a key in the idToAutomationRootNode map. The callback to enableTab is is // bound to the callback passed in to getTree(), so that once the tree is // available (either due to having been cached earlier, or after an // accessibility event occurs which causes the tree to be populated), the @@ -48,8 +48,8 @@ automation.registerCustomHook(function(bindingsAPI) { callback(); return; } - var id = createAutomationTreeID(pid, rid); - var targetTree = idToAutomationTree[id]; + var id = createAutomationRootNodeID(pid, rid); + 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. @@ -65,7 +65,7 @@ automation.registerCustomHook(function(bindingsAPI) { var desktopTree = null; apiFunctions.setHandleRequest('getDesktop', function(callback) { - desktopTree = idToAutomationTree[DESKTOP_TREE_ID]; + desktopTree = idToAutomationRootNode[DESKTOP_TREE_ID]; if (!desktopTree) { if (DESKTOP_TREE_ID in idToCallback) idToCallback[DESKTOP_TREE_ID].push(callback); @@ -76,7 +76,7 @@ automation.registerCustomHook(function(bindingsAPI) { // scope. automationInternal.enableDesktop(function() { if (lastError.hasError(chrome)) { - delete idToAutomationTree[DESKTOP_TREE_ID]; + delete idToAutomationRootNode[DESKTOP_TREE_ID]; callback(); return; } @@ -93,14 +93,14 @@ automation.registerCustomHook(function(bindingsAPI) { automationInternal.onAccessibilityEvent.addListener(function(data) { var pid = data.processID; var rid = data.routingID; - var id = createAutomationTreeID(pid, rid); - var targetTree = idToAutomationTree[id]; + var id = createAutomationRootNodeID(pid, rid); + 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 AutomationTree(pid, rid); - idToAutomationTree[id] = targetTree; + targetTree = new AutomationRootNode(pid, rid); + idToAutomationRootNode[id] = targetTree; } privates(targetTree).impl.update(data); var eventType = data.eventType; diff --git a/chrome/renderer/resources/renderer_resources.grd b/chrome/renderer/resources/renderer_resources.grd index a419012..c0d2741 100644 --- a/chrome/renderer/resources/renderer_resources.grd +++ b/chrome/renderer/resources/renderer_resources.grd @@ -37,7 +37,6 @@ <include name="IDR_AUTOMATION_CUSTOM_BINDINGS_JS" file="extensions\automation_custom_bindings.js" type="BINDATA" /> <include name="IDR_AUTOMATION_EVENT_JS" file="extensions\automation\automation_event.js" type="BINDATA" /> <include name="IDR_AUTOMATION_NODE_JS" file="extensions\automation\automation_node.js" type="BINDATA" /> - <include name="IDR_AUTOMATION_TREE_JS" file="extensions\automation\automation_tree.js" type="BINDATA" /> <include name="IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS" file="extensions\browser_action_custom_bindings.js" type="BINDATA" /> <include name="IDR_CAST_STREAMING_RTP_STREAM_CUSTOM_BINDINGS_JS" file="extensions\cast_streaming_rtp_stream_custom_bindings.js" type="BINDATA" /> <include name="IDR_CAST_STREAMING_SESSION_CUSTOM_BINDINGS_JS" file="extensions\cast_streaming_session_custom_bindings.js" type="BINDATA" /> diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js index eb27ef4..c50f3f3 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js +++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js @@ -4,7 +4,7 @@ var allTests = [ function testDoDefault() { - var firstTextField = findAutomationNode(tree.root, + var firstTextField = findAutomationNode(rootNode, function(node) { return node.role == 'textField'; }); @@ -16,7 +16,7 @@ var allTests = [ }, function testFocus() { - var firstFocusableNode = findAutomationNode(tree.root, + var firstFocusableNode = findAutomationNode(rootNode, function(node) { return node.role == 'button' && node.state.focusable; }); diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/common.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/common.js index 1887fc8..61d5a6b 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/desktop/common.js +++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/common.js @@ -10,7 +10,7 @@ var EventType = chrome.automation.EventType; var RoleType = chrome.automation.RoleType; var StateType = chrome.automation.StateType; -var tree = null; +var rootNode = null; function findAutomationNode(root, condition) { if (condition(root)) @@ -26,8 +26,8 @@ function findAutomationNode(root, condition) { } function setupAndRunTests(allTests) { - chrome.automation.getDesktop(function(treeArg) { - tree = treeArg; + chrome.automation.getDesktop(function(rootNodeArg) { + rootNode = rootNodeArg; chrome.test.runTests(allTests); }); } diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js index 3ea84c4..12a2297 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js +++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js @@ -4,30 +4,30 @@ var allTests = [ function testGetDesktop() { - chrome.automation.getDesktop(function(tree) { - assertEq(RoleType.desktop, tree.root.role); - assertEq(RoleType.window, tree.root.firstChild().role); + chrome.automation.getDesktop(function(rootNode) { + assertEq(RoleType.desktop, rootNode.role); + assertEq(RoleType.window, rootNode.firstChild().role); chrome.test.succeed(); }); }, function testGetDesktopTwice() { var desktop = null; - chrome.automation.getDesktop(function(tree) { - desktop = tree; + chrome.automation.getDesktop(function(rootNode) { + desktop = rootNode; }); - chrome.automation.getDesktop(function(tree) { - assertEq(tree, desktop); + chrome.automation.getDesktop(function(rootNode) { + assertEq(rootNode, desktop); chrome.test.succeed(); }); }, function testGetDesktopNested() { var desktop = null; - chrome.automation.getDesktop(function(tree) { - desktop = tree; - chrome.automation.getDesktop(function(tree2) { - assertEq(tree2, desktop); + chrome.automation.getDesktop(function(rootNode) { + desktop = rootNode; + chrome.automation.getDesktop(function(rootNode2) { + assertEq(rootNode2, desktop); chrome.test.succeed(); }); }); diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js index 911c988..e9387b9 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js @@ -4,7 +4,7 @@ var allTests = [ function testSimpleAction() { - var okButton = tree.root.firstChild().firstChild(); + var okButton = rootNode.firstChild().firstChild(); okButton.addEventListener(EventType.focus, function() { chrome.test.succeed(); }, true); diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/common.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/common.js index 753899f..b794e0cf 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs/common.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/common.js @@ -10,7 +10,7 @@ var EventType = chrome.automation.EventType; var RoleType = chrome.automation.RoleType; var StateType = chrome.automation.StateType; -var tree = null; +var rootNode = null; function createTab(url, callback) { chrome.tabs.create({"url": url}, function(tab) { @@ -21,9 +21,9 @@ function createTab(url, callback) { function setUpAndRunTests(allTests) { getUrlFromConfig(function(url) { createTab(url, function(unused_tab) { - chrome.automation.getTree(function (returnedTree) { - tree = returnedTree; - tree.addEventListener('loadComplete', function() { + chrome.automation.getTree(function (returnedRootNode) { + rootNode = returnedRootNode; + rootNode.addEventListener('loadComplete', function() { chrome.test.runTests(allTests); }); }); diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js index 065d03c..f354a94 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js @@ -4,7 +4,7 @@ var allTests = [ function testEventListenerTarget() { - var cancelButton = tree.root.firstChild().children()[2]; + var cancelButton = rootNode.firstChild().children()[2]; assertEq('Cancel', cancelButton.attributes.name); cancelButton.addEventListener(EventType.focus, function onFocusTarget(event) { @@ -16,7 +16,7 @@ var allTests = [ cancelButton.focus(); }, function testEventListenerBubble() { - var cancelButton = tree.root.firstChild().children()[2]; + var cancelButton = rootNode.firstChild().children()[2]; assertEq('Cancel', cancelButton.attributes.name); var cancelButtonGotEvent = false; cancelButton.addEventListener(EventType.focus, @@ -24,21 +24,21 @@ var allTests = [ cancelButtonGotEvent = true; cancelButton.removeEventListener(EventType.focus, onFocusBubble); }); - tree.root.addEventListener(EventType.focus, + rootNode.addEventListener(EventType.focus, function onFocusBubbleRoot(event) { assertEq('focus', event.type); assertEq(cancelButton, event.target); assertTrue(cancelButtonGotEvent); - tree.root.removeEventListener(EventType.focus, onFocusBubbleRoot); + rootNode.removeEventListener(EventType.focus, onFocusBubbleRoot); chrome.test.succeed(); }); cancelButton.focus(); }, function testStopPropagation() { - var cancelButton = tree.root.firstChild().children()[2]; + var cancelButton = rootNode.firstChild().children()[2]; assertEq('Cancel', cancelButton.attributes.name); function onFocusStopPropRoot(event) { - tree.root.removeEventListener(EventType.focus, onFocusStopPropRoot); + rootNode.removeEventListener(EventType.focus, onFocusStopPropRoot); chrome.test.fail("Focus event was propagated to root"); }; cancelButton.addEventListener(EventType.focus, @@ -46,15 +46,15 @@ var allTests = [ cancelButton.removeEventListener(EventType.focus, onFocusStopProp); event.stopPropagation(); window.setTimeout((function() { - tree.root.removeEventListener(EventType.focus, onFocusStopPropRoot); + rootNode.removeEventListener(EventType.focus, onFocusStopPropRoot); chrome.test.succeed(); }).bind(this), 0); }); - tree.root.addEventListener(EventType.focus, onFocusStopPropRoot); + rootNode.addEventListener(EventType.focus, onFocusStopPropRoot); cancelButton.focus(); }, function testEventListenerCapture() { - var cancelButton = tree.root.firstChild().children()[2]; + var cancelButton = rootNode.firstChild().children()[2]; assertEq('Cancel', cancelButton.attributes.name); var cancelButtonGotEvent = false; function onFocusCapture(event) { @@ -63,14 +63,14 @@ var allTests = [ chrome.test.fail("Focus event was not captured by root"); }; cancelButton.addEventListener(EventType.focus, onFocusCapture); - tree.root.addEventListener(EventType.focus, + rootNode.addEventListener(EventType.focus, function onFocusCaptureRoot(event) { assertEq('focus', event.type); assertEq(cancelButton, event.target); assertFalse(cancelButtonGotEvent); event.stopPropagation(); - tree.root.removeEventListener(EventType.focus, onFocusCaptureRoot); - tree.root.removeEventListener(EventType.focus, onFocusCapture); + rootNode.removeEventListener(EventType.focus, onFocusCaptureRoot); + rootNode.removeEventListener(EventType.focus, onFocusCapture); window.setTimeout(chrome.test.succeed.bind(this), 0); }, true); cancelButton.focus(); diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js index da6d71c..97cb461 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js @@ -5,21 +5,21 @@ var allTests = [ function testLocation() { function assertOkButtonLocation(event) { - var okButton = tree.root.firstChild().firstChild(); + var okButton = rootNode.firstChild().firstChild(); assertTrue('location' in okButton); assertEq({left:100, top: 200, width: 300, height: 400}, okButton.location); chrome.test.succeed(); }; - var okButton = tree.root.firstChild().firstChild(); + var okButton = rootNode.firstChild().firstChild(); assertTrue('location' in okButton, 'no location in okButton'); assertTrue('left' in okButton.location, 'no left in location'); assertTrue('top' in okButton.location, 'no top in location'); assertTrue('height' in okButton.location, 'no height in location'); assertTrue('width' in okButton.location, 'no width in location'); - tree.root.addEventListener( + rootNode.addEventListener( EventType.childrenChanged, assertOkButtonLocation); chrome.tabs.executeScript({ 'code': 'document.querySelector("button")' + 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 73d5d2d..4667fc9 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 @@ -12,13 +12,13 @@ var RemoveUntestedStates = function(state) { var allTests = [ function testSimplePage() { - var title = tree.root.attributes.docTitle; + var title = rootNode.attributes.docTitle; assertEq('Automation Tests', title); - RemoveUntestedStates(tree.root.state); + RemoveUntestedStates(rootNode.state); assertEq( {enabled: true, focusable: true, readOnly: true}, - tree.root.state); - var children = tree.root.children(); + rootNode.state); + var children = rootNode.children(); assertEq(1, children.length); var body = children[0]; assertEq('body', body.attributes.htmlTag); @@ -48,11 +48,11 @@ var allTests = [ cancelButton.state); // Traversal. - assertEq(undefined, tree.root.parent()); - assertEq(tree.root, body.parent()); + assertEq(undefined, rootNode.parent()); + assertEq(rootNode, body.parent()); - assertEq(body, tree.root.firstChild()); - assertEq(body, tree.root.lastChild()); + assertEq(body, rootNode.firstChild()); + assertEq(body, rootNode.lastChild()); assertEq(okButton, body.firstChild()); assertEq(cancelButton, body.lastChild()); @@ -73,6 +73,11 @@ var allTests = [ assertEq(undefined, cancelButton.nextSibling()); chrome.test.succeed(); + }, + function testIsRoot() { + assertTrue(rootNode.isRootNode); + assertFalse(rootNode.firstChild().isRootNode); + chrome.test.succeed(); } ]; diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js index e1ae825..8c26df4 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js @@ -20,9 +20,9 @@ var allTests = [ // Keep the NTP as the active tab so that we know we're requesting the // tab by ID rather than just getting the active tab still. createBackgroundTab(url, function(tab) { - chrome.automation.getTree(tab.id, function(tree) { - tree.addEventListener('loadComplete', function() { - var title = tree.root.attributes['docTitle']; + chrome.automation.getTree(tab.id, function(rootNode) { + rootNode.addEventListener('loadComplete', function() { + var title = rootNode.attributes['docTitle']; chrome.test.assertEq('Automation Tests', title); chrome.test.succeed(); }); diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/actions.js b/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/actions.js index 9aa9fdd..ec80375 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/actions.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/actions.js @@ -6,7 +6,7 @@ var allTests = [ function testError() { chrome.automation.getTree(function(tree) { try { - tree.root.focus(); + rootNode.focus(); } catch (e) { chrome.test.assertEq('focus requires {"desktop": true} or' + ' {"interact": true} in the "automation" manifest key.', diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/common.js b/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/common.js index dc9cd1d..fed7094 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/common.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/common.js @@ -6,7 +6,7 @@ var assertEq = chrome.test.assertEq; var assertFalse = chrome.test.assertFalse; var assertTrue = chrome.test.assertTrue; -var tree = null; +var rootNode = null; function setUpAndRunTests(allTests) { chrome.test.getConfig(function(config) { @@ -14,9 +14,9 @@ function setUpAndRunTests(allTests) { var url = 'http://a.com:PORT/index.html' .replace(/PORT/, config.testServer.port); - function gotTree(returnedTree) { - tree = returnedTree; - tree.addEventListener('loadComplete', function() { + function gotTree(returnedRootNode) { + rootNode = returnedRootNode; + rootNode.addEventListener('loadComplete', function() { chrome.test.runTests(allTests); }); } diff --git a/extensions/renderer/resources/utils.js b/extensions/renderer/resources/utils.js index dd1ec2a..853c652 100644 --- a/extensions/renderer/resources/utils.js +++ b/extensions/renderer/resources/utils.js @@ -72,15 +72,18 @@ function loadTypeSchema(typeName, defaultSchema) { * instance of the implementation class). * @param {string} name The name of the exposed wrapper class. * @param {Object} cls The class implementation. - * @param {{functions: ?Array.<string>, + * @param {{superclass: ?Function, + * functions: ?Array.<string>, * properties: ?Array.<string>, * readonly: ?Array.<string>}} exposed The names of properties on the - * implementation class to be exposed. |functions| represents the names of - * functions which should be delegated to the implementation; |properties| - * are gettable/settable properties and |readonly| are read-only properties. + * implementation class to be exposed. |superclass| represents the + * constructor of the class to be used as the superclass of the exposed + * class; |functions| represents the names of functions which should be + * delegated to the implementation; |properties| are gettable/settable + * properties and |readonly| are read-only properties. */ function expose(name, cls, exposed) { - var publicClass = createClassWrapper(name, cls); + var publicClass = createClassWrapper(name, cls, exposed.superclass); if ('functions' in exposed) { $Array.forEach(exposed.functions, function(func) { diff --git a/extensions/renderer/utils_native_handler.cc b/extensions/renderer/utils_native_handler.cc index 5c8bf5d..1c0941f 100644 --- a/extensions/renderer/utils_native_handler.cc +++ b/extensions/renderer/utils_native_handler.cc @@ -21,25 +21,34 @@ UtilsNativeHandler::~UtilsNativeHandler() {} void UtilsNativeHandler::CreateClassWrapper( const v8::FunctionCallbackInfo<v8::Value>& args) { - CHECK_EQ(2, args.Length()); + CHECK_EQ(3, args.Length()); CHECK(args[0]->IsString()); std::string name = *v8::String::Utf8Value(args[0]); CHECK(args[1]->IsObject()); - v8::Local<v8::Object> obj = args[1].As<v8::Object>(); + v8::Local<v8::Object> cls = args[1].As<v8::Object>(); + CHECK(args[2]->IsObject() || args[2]->IsUndefined()); + v8::Local<v8::Value> superclass = args[2]; v8::HandleScope handle_scope(GetIsolate()); // TODO(fsamuel): Consider moving the source wrapping to ModuleSystem. v8::Handle<v8::String> source = v8::String::NewFromUtf8( GetIsolate(), base::StringPrintf( - "(function($Object, $Function, privates, cls) {" + "(function($Object, $Function, privates, cls, superclass) {" "'use strict';\n" - " return function %s() {\n" - " var privateObj = $Object.create(cls.prototype);\n" - " $Function.apply(cls, privateObj, arguments);\n" - " privateObj.wrapper = this;\n" - " privates(this).impl = privateObj;\n" - "}})", + " function %s() {\n" + " var privateObj = $Object.create(cls.prototype);\n" + " $Function.apply(cls, privateObj, arguments);\n" + " privateObj.wrapper = this;\n" + " privates(this).impl = privateObj;\n" + " };\n" + " if (superclass) {\n" + " %s.prototype = Object.create(superclass.prototype);\n" + " }\n" + " return %s;\n" + "})", + name.c_str(), + name.c_str(), name.c_str()).c_str()); v8::Handle<v8::Value> func_as_value = context()->module_system()->RunString( source, v8::String::NewFromUtf8(GetIsolate(), name.c_str())); @@ -57,7 +66,8 @@ void UtilsNativeHandler::CreateClassWrapper( context()->safe_builtins()->GetFunction(), natives->Get(v8::String::NewFromUtf8( GetIsolate(), "privates", v8::String::kInternalizedString)), - obj}; + cls, + superclass}; v8::Local<v8::Value> result; { v8::TryCatch try_catch; |