summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoraboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-10 18:37:14 +0000
committeraboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-10 18:37:14 +0000
commited51006db1465dc8542bb6508514fd89414f3f3b (patch)
tree6972994063c5793502e7a96882360d4dda516c87
parent09f60dd57009ea1ac711bf6592e9d90fe66a9e78 (diff)
downloadchromium_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
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/extensions/api/automation.idl44
-rw-r--r--chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc1
-rw-r--r--chrome/renderer/extensions/utils_unittest.cc123
-rw-r--r--chrome/renderer/resources/extensions/automation/automation_node.js212
-rw-r--r--chrome/renderer/resources/extensions/automation/automation_tree.js187
-rw-r--r--chrome/renderer/resources/extensions/automation_custom_bindings.js30
-rw-r--r--chrome/renderer/resources/renderer_resources.grd1
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js4
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/desktop/common.js6
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js22
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js2
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/common.js8
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/events.js24
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/location.js6
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js21
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js6
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/actions.js2
-rw-r--r--chrome/test/data/extensions/api_test/automation/tests/tabs_automation_boolean/common.js8
-rw-r--r--extensions/renderer/resources/utils.js13
-rw-r--r--extensions/renderer/utils_native_handler.cc30
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;