1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * Enum for WebDriver status codes.
10 STALE_ELEMENT_REFERENCE: 10,
15 * Enum for node types.
24 * Dictionary key to use for holding an element ID.
28 var ELEMENT_KEY = 'ELEMENT';
31 * True if shadow dom is enabled.
35 var SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function';
38 * A cache which maps IDs <-> cached objects for the purpose of identifying
39 * a script object remotely.
45 this.idPrefix_ = Math.random().toString();
51 * Stores a given item in the cache and returns a unique ID.
53 * @param {!Object} item The item to store in the cache.
54 * @return {number} The ID for the cached item.
56 storeItem: function(item) {
57 for (var i in this.cache_) {
58 if (item == this.cache_[i])
61 var id = this.idPrefix_ + '-' + this.nextId_;
62 this.cache_[id] = item;
68 * Retrieves the cached object for the given ID.
70 * @param {number} id The ID for the cached item to retrieve.
71 * @return {!Object} The retrieved item.
73 retrieveItem: function(id) {
74 var item = this.cache_[id];
77 var error = new Error('not in cache');
78 error.code = StatusCode.STALE_ELEMENT_REFERENCE;
79 error.message = 'element is not attached to the page document';
84 * Clears stale items from the cache.
86 clearStale: function() {
87 for (var id in this.cache_) {
88 var node = this.cache_[id];
89 if (!this.isNodeReachable_(node))
90 delete this.cache_[id];
96 * @param {!Node} node The node to check.
97 * @return {boolean} If the nodes is reachable.
99 isNodeReachable_: function(node) {
100 var nodeRoot = getNodeRoot(node);
101 if (nodeRoot == document)
103 else if (SHADOW_DOM_ENABLED && nodeRoot instanceof ShadowRoot)
111 * Returns the root element of the node. Found by traversing parentNodes until
112 * a node with no parent is found. This node is considered the root.
113 * @param {!Node} node The node to find the root element for.
114 * @return {!Node} The root node.
116 function getNodeRoot(node) {
117 while (node.parentNode) {
118 node = node.parentNode;
124 * Returns the global object cache for the page.
125 * @param {Document=} opt_doc The document whose cache to retrieve. Defaults to
126 * the current document.
127 * @return {!Cache} The page's object cache.
129 function getPageCache(opt_doc) {
130 var doc = opt_doc || document;
131 var key = '$cdc_asdjflasutopfhvcZLmcfl_';
133 doc[key] = new Cache();
138 * Wraps the given value to be transmitted remotely by converting
139 * appropriate objects to cached object IDs.
141 * @param {*} value The value to wrap.
142 * @return {*} The wrapped value.
144 function wrap(value) {
145 if (typeof(value) == 'object' && value != null) {
146 var nodeType = value['nodeType'];
147 if (nodeType == NodeType.ELEMENT || nodeType == NodeType.DOCUMENT
148 || (SHADOW_DOM_ENABLED && value instanceof ShadowRoot)) {
150 var root = getNodeRoot(value);
151 wrapped[ELEMENT_KEY] = getPageCache(root).storeItem(value);
155 var obj = (typeof(value.length) == 'number') ? [] : {};
156 for (var prop in value)
157 obj[prop] = wrap(value[prop]);
164 * Unwraps the given value by converting from object IDs to the cached
167 * @param {*} value The value to unwrap.
168 * @param {Cache} cache The cache to retrieve wrapped elements from.
169 * @return {*} The unwrapped value.
171 function unwrap(value, cache) {
172 if (typeof(value) == 'object' && value != null) {
173 if (ELEMENT_KEY in value)
174 return cache.retrieveItem(value[ELEMENT_KEY]);
176 var obj = (typeof(value.length) == 'number') ? [] : {};
177 for (var prop in value)
178 obj[prop] = unwrap(value[prop], cache);
185 * Calls a given function and returns its value.
187 * The inputs to and outputs of the function will be unwrapped and wrapped
188 * respectively, unless otherwise specified. This wrapping involves converting
189 * between cached object reference IDs and actual JS objects. The cache will
190 * automatically be pruned each call to remove stale references.
192 * @param {Array.<string>} shadowHostIds The host ids of the nested shadow
193 * DOMs the function should be executed in the context of.
194 * @param {function(...[*]) : *} func The function to invoke.
195 * @param {!Array.<*>} args The array of arguments to supply to the function,
196 * which will be unwrapped before invoking the function.
197 * @param {boolean=} opt_unwrappedReturn Whether the function's return value
198 * should be left unwrapped.
199 * @return {*} An object containing a status and value property, where status
200 * is a WebDriver status code and value is the wrapped value. If an
201 * unwrapped return was specified, this will be the function's pure return
204 function callFunction(shadowHostIds, func, args, opt_unwrappedReturn) {
205 var cache = getPageCache();
207 if (shadowHostIds && SHADOW_DOM_ENABLED) {
208 for (var i = 0; i < shadowHostIds.length; i++) {
209 var host = cache.retrieveItem(shadowHostIds[i]);
210 // TODO(zachconrad): Use the olderShadowRoot API when available to check
211 // all of the shadow roots.
212 cache = getPageCache(host.webkitShadowRoot);
217 if (opt_unwrappedReturn)
218 return func.apply(null, unwrap(args, cache));
222 var returnValue = wrap(func.apply(null, unwrap(args, cache)));
224 status = error.code || StatusCode.UNKNOWN_ERROR;
225 var returnValue = error.message;