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
= getNodeRootThroughAnyShadows(node
);
101 return (nodeRoot
== document
);
106 * Returns the root element of the node. Found by traversing parentNodes until
107 * a node with no parent is found. This node is considered the root.
108 * @param {!Node} node The node to find the root element for.
109 * @return {!Node} The root node.
111 function getNodeRoot(node
) {
112 while (node
.parentNode
) {
113 node
= node
.parentNode
;
119 * Returns the root element of the node, jumping up through shadow roots if
122 function getNodeRootThroughAnyShadows(node
) {
123 var root
= getNodeRoot(node
);
124 while (SHADOW_DOM_ENABLED
&& root
instanceof ShadowRoot
) {
125 root
= getNodeRoot(root
.host
);
131 * Returns the global object cache for the page.
132 * @param {Document=} opt_doc The document whose cache to retrieve. Defaults to
133 * the current document.
134 * @return {!Cache} The page's object cache.
136 function getPageCache(opt_doc
) {
137 var doc
= opt_doc
|| document
;
138 var key
= '$cdc_asdjflasutopfhvcZLmcfl_';
140 doc
[key
] = new Cache();
145 * Wraps the given value to be transmitted remotely by converting
146 * appropriate objects to cached object IDs.
148 * @param {*} value The value to wrap.
149 * @return {*} The wrapped value.
151 function wrap(value
) {
152 if (typeof(value
) == 'object' && value
!= null) {
153 var nodeType
= value
['nodeType'];
154 if (nodeType
== NodeType
.ELEMENT
|| nodeType
== NodeType
.DOCUMENT
155 || (SHADOW_DOM_ENABLED
&& value
instanceof ShadowRoot
)) {
157 var root
= getNodeRootThroughAnyShadows(value
);
158 wrapped
[ELEMENT_KEY
] = getPageCache(root
).storeItem(value
);
162 var obj
= (typeof(value
.length
) == 'number') ? [] : {};
163 for (var prop
in value
)
164 obj
[prop
] = wrap(value
[prop
]);
171 * Unwraps the given value by converting from object IDs to the cached
174 * @param {*} value The value to unwrap.
175 * @param {Cache} cache The cache to retrieve wrapped elements from.
176 * @return {*} The unwrapped value.
178 function unwrap(value
, cache
) {
179 if (typeof(value
) == 'object' && value
!= null) {
180 if (ELEMENT_KEY
in value
)
181 return cache
.retrieveItem(value
[ELEMENT_KEY
]);
183 var obj
= (typeof(value
.length
) == 'number') ? [] : {};
184 for (var prop
in value
)
185 obj
[prop
] = unwrap(value
[prop
], cache
);
192 * Calls a given function and returns its value.
194 * The inputs to and outputs of the function will be unwrapped and wrapped
195 * respectively, unless otherwise specified. This wrapping involves converting
196 * between cached object reference IDs and actual JS objects. The cache will
197 * automatically be pruned each call to remove stale references.
199 * @param {Array<string>} shadowHostIds The host ids of the nested shadow
200 * DOMs the function should be executed in the context of.
201 * @param {function(...[*]) : *} func The function to invoke.
202 * @param {!Array<*>} args The array of arguments to supply to the function,
203 * which will be unwrapped before invoking the function.
204 * @param {boolean=} opt_unwrappedReturn Whether the function's return value
205 * should be left unwrapped.
206 * @return {*} An object containing a status and value property, where status
207 * is a WebDriver status code and value is the wrapped value. If an
208 * unwrapped return was specified, this will be the function's pure return
211 function callFunction(shadowHostIds
, func
, args
, opt_unwrappedReturn
) {
212 var cache
= getPageCache();
214 if (shadowHostIds
&& SHADOW_DOM_ENABLED
) {
215 for (var i
= 0; i
< shadowHostIds
.length
; i
++) {
216 var host
= cache
.retrieveItem(shadowHostIds
[i
]);
217 // TODO(zachconrad): Use the olderShadowRoot API when available to check
218 // all of the shadow roots.
219 cache
= getPageCache(host
.webkitShadowRoot
);
224 if (opt_unwrappedReturn
)
225 return func
.apply(null, unwrap(args
, cache
));
229 var returnValue
= wrap(func
.apply(null, unwrap(args
, cache
)));
231 status
= error
.code
|| StatusCode
.UNKNOWN_ERROR
;
232 var returnValue
= error
.message
;