OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Enum for WebDriver status codes. | 6 * Enum for WebDriver status codes. |
7 * @enum {number} | 7 * @enum {number} |
8 */ | 8 */ |
9 var StatusCode = { | 9 var StatusCode = { |
10 STALE_ELEMENT_REFERENCE: 10, | 10 STALE_ELEMENT_REFERENCE: 10, |
(...skipping 10 matching lines...) Expand all Loading... |
21 }; | 21 }; |
22 | 22 |
23 /** | 23 /** |
24 * Dictionary key to use for holding an element ID. | 24 * Dictionary key to use for holding an element ID. |
25 * @const | 25 * @const |
26 * @type {string} | 26 * @type {string} |
27 */ | 27 */ |
28 var ELEMENT_KEY = 'ELEMENT'; | 28 var ELEMENT_KEY = 'ELEMENT'; |
29 | 29 |
30 /** | 30 /** |
| 31 * True if using W3C Element references. |
| 32 * @const |
| 33 * @type {boolean} |
| 34 */ |
| 35 var w3cEnabled = false; |
| 36 |
| 37 /** |
31 * True if shadow dom is enabled. | 38 * True if shadow dom is enabled. |
32 * @const | 39 * @const |
33 * @type {boolean} | 40 * @type {boolean} |
34 */ | 41 */ |
35 var SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function'; | 42 var SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function'; |
36 | 43 |
37 /** | 44 /** |
| 45 * Generates a unique ID to identify an element. |
| 46 * @void |
| 47 * @return {string} Randomly generated ID. |
| 48 */ |
| 49 function generateUUID() { |
| 50 var array = new Uint8Array(16); |
| 51 window.crypto.getRandomValues(array); |
| 52 array[6] = 0x40 | (array[6] & 0x0f); |
| 53 array[8] = 0x80 | (array[8] & 0x3f); |
| 54 |
| 55 var UUID = ""; |
| 56 for (var i = 0; i < 16; i++) { |
| 57 var temp = array[i].toString(16); |
| 58 if (temp.length < 2) |
| 59 temp = "0" + temp; |
| 60 UUID += temp; |
| 61 if (i == 3 || i == 5 || i == 7 || i == 9) |
| 62 UUID += "-"; |
| 63 } |
| 64 return UUID; |
| 65 }; |
| 66 |
| 67 /** |
| 68 * A cache which maps IDs <-> cached objects for the purpose of identifying |
| 69 * a script object remotely. Uses UUIDs for identification. |
| 70 * @constructor |
| 71 */ |
| 72 function CacheWithUUID() { |
| 73 this.cache_ = {}; |
| 74 } |
| 75 |
| 76 CacheWithUUID.prototype = { |
| 77 /** |
| 78 * Stores a given item in the cache and returns a unique UUID. |
| 79 * |
| 80 * @param {!Object} item The item to store in the cache. |
| 81 * @return {number} The UUID for the cached item. |
| 82 */ |
| 83 storeItem: function(item) { |
| 84 for (var i in this.cache_) { |
| 85 if (item == this.cache_[i]) |
| 86 return i; |
| 87 } |
| 88 var id = generateUUID(); |
| 89 this.cache_[id] = item; |
| 90 return id; |
| 91 }, |
| 92 |
| 93 /** |
| 94 * Retrieves the cached object for the given ID. |
| 95 * |
| 96 * @param {number} id The ID for the cached item to retrieve. |
| 97 * @return {!Object} The retrieved item. |
| 98 */ |
| 99 retrieveItem: function(id) { |
| 100 var item = this.cache_[id]; |
| 101 if (item) |
| 102 return item; |
| 103 var error = new Error('not in cache'); |
| 104 error.code = StatusCode.STALE_ELEMENT_REFERENCE; |
| 105 error.message = 'element is not attached to the page document'; |
| 106 throw error; |
| 107 }, |
| 108 |
| 109 /** |
| 110 * Clears stale items from the cache. |
| 111 */ |
| 112 clearStale: function() { |
| 113 for (var id in this.cache_) { |
| 114 var node = this.cache_[id]; |
| 115 if (!this.isNodeReachable_(node)) |
| 116 delete this.cache_[id]; |
| 117 } |
| 118 }, |
| 119 |
| 120 /** |
| 121 * @private |
| 122 * @param {!Node} node The node to check. |
| 123 * @return {boolean} If the nodes is reachable. |
| 124 */ |
| 125 isNodeReachable_: function(node) { |
| 126 var nodeRoot = getNodeRootThroughAnyShadows(node); |
| 127 return (nodeRoot == document); |
| 128 } |
| 129 |
| 130 |
| 131 }; |
| 132 |
| 133 /** |
38 * A cache which maps IDs <-> cached objects for the purpose of identifying | 134 * A cache which maps IDs <-> cached objects for the purpose of identifying |
39 * a script object remotely. | 135 * a script object remotely. |
40 * @constructor | 136 * @constructor |
41 */ | 137 */ |
42 function Cache() { | 138 function Cache() { |
43 this.cache_ = {}; | 139 this.cache_ = {}; |
44 this.nextId_ = 1; | 140 this.nextId_ = 1; |
45 this.idPrefix_ = Math.random().toString(); | 141 this.idPrefix_ = Math.random().toString(); |
46 } | 142 } |
47 | 143 |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
126 } | 222 } |
127 return root; | 223 return root; |
128 } | 224 } |
129 | 225 |
130 /** | 226 /** |
131 * Returns the global object cache for the page. | 227 * Returns the global object cache for the page. |
132 * @param {Document=} opt_doc The document whose cache to retrieve. Defaults to | 228 * @param {Document=} opt_doc The document whose cache to retrieve. Defaults to |
133 * the current document. | 229 * the current document. |
134 * @return {!Cache} The page's object cache. | 230 * @return {!Cache} The page's object cache. |
135 */ | 231 */ |
136 function getPageCache(opt_doc) { | 232 function getPageCache(opt_doc, opt_w3c) { |
137 var doc = opt_doc || document; | 233 var doc = opt_doc || document; |
| 234 var w3c = opt_w3c || false; |
138 var key = '$cdc_asdjflasutopfhvcZLmcfl_'; | 235 var key = '$cdc_asdjflasutopfhvcZLmcfl_'; |
139 if (!(key in doc)) | 236 if (w3c) { |
140 doc[key] = new Cache(); | 237 if (!(key in doc)) |
141 return doc[key]; | 238 doc[key] = new CacheWithUUID(); |
| 239 return doc[key]; |
| 240 } else { |
| 241 if (!(key in doc)) |
| 242 doc[key] = new Cache(); |
| 243 return doc[key]; |
| 244 } |
142 } | 245 } |
143 | 246 |
144 /** | 247 /** |
145 * Wraps the given value to be transmitted remotely by converting | 248 * Wraps the given value to be transmitted remotely by converting |
146 * appropriate objects to cached object IDs. | 249 * appropriate objects to cached object IDs. |
147 * | 250 * |
148 * @param {*} value The value to wrap. | 251 * @param {*} value The value to wrap. |
149 * @return {*} The wrapped value. | 252 * @return {*} The wrapped value. |
150 */ | 253 */ |
151 function wrap(value) { | 254 function wrap(value) { |
152 // As of crrev.com/1316933002, typeof() for some elements will return | 255 // As of crrev.com/1316933002, typeof() for some elements will return |
153 // 'function', not 'object'. So we need to check for both non-null objects, as | 256 // 'function', not 'object'. So we need to check for both non-null objects, as |
154 // well Elements that also happen to be callable functions (e.g. <embed> and | 257 // well Elements that also happen to be callable functions (e.g. <embed> and |
155 // <object> elements). Note that we can not use |value instanceof Object| here | 258 // <object> elements). Note that we can not use |value instanceof Object| here |
156 // since this does not work with frames/iframes, for example | 259 // since this does not work with frames/iframes, for example |
157 // frames[0].document.body instanceof Object == false even though | 260 // frames[0].document.body instanceof Object == false even though |
158 // typeof(frames[0].document.body) == 'object'. | 261 // typeof(frames[0].document.body) == 'object'. |
159 if ((typeof(value) == 'object' && value != null) || | 262 if ((typeof(value) == 'object' && value != null) || |
160 (typeof(value) == 'function' && value instanceof Element)) { | 263 (typeof(value) == 'function' && value instanceof Element)) { |
161 var nodeType = value['nodeType']; | 264 var nodeType = value['nodeType']; |
162 if (nodeType == NodeType.ELEMENT || nodeType == NodeType.DOCUMENT | 265 if (nodeType == NodeType.ELEMENT || nodeType == NodeType.DOCUMENT |
163 || (SHADOW_DOM_ENABLED && value instanceof ShadowRoot)) { | 266 || (SHADOW_DOM_ENABLED && value instanceof ShadowRoot)) { |
164 var wrapped = {}; | 267 var wrapped = {}; |
165 var root = getNodeRootThroughAnyShadows(value); | 268 var root = getNodeRootThroughAnyShadows(value); |
166 wrapped[ELEMENT_KEY] = getPageCache(root).storeItem(value); | 269 wrapped[ELEMENT_KEY] = getPageCache(root, w3cEnabled).storeItem(value); |
167 return wrapped; | 270 return wrapped; |
168 } | 271 } |
169 | 272 |
170 var obj = (typeof(value.length) == 'number') ? [] : {}; | 273 var obj = (typeof(value.length) == 'number') ? [] : {}; |
171 for (var prop in value) | 274 for (var prop in value) |
172 obj[prop] = wrap(value[prop]); | 275 obj[prop] = wrap(value[prop]); |
173 return obj; | 276 return obj; |
174 } | 277 } |
175 return value; | 278 return value; |
176 } | 279 } |
(...skipping 25 matching lines...) Expand all Loading... |
202 * The inputs to and outputs of the function will be unwrapped and wrapped | 305 * The inputs to and outputs of the function will be unwrapped and wrapped |
203 * respectively, unless otherwise specified. This wrapping involves converting | 306 * respectively, unless otherwise specified. This wrapping involves converting |
204 * between cached object reference IDs and actual JS objects. The cache will | 307 * between cached object reference IDs and actual JS objects. The cache will |
205 * automatically be pruned each call to remove stale references. | 308 * automatically be pruned each call to remove stale references. |
206 * | 309 * |
207 * @param {Array<string>} shadowHostIds The host ids of the nested shadow | 310 * @param {Array<string>} shadowHostIds The host ids of the nested shadow |
208 * DOMs the function should be executed in the context of. | 311 * DOMs the function should be executed in the context of. |
209 * @param {function(...[*]) : *} func The function to invoke. | 312 * @param {function(...[*]) : *} func The function to invoke. |
210 * @param {!Array<*>} args The array of arguments to supply to the function, | 313 * @param {!Array<*>} args The array of arguments to supply to the function, |
211 * which will be unwrapped before invoking the function. | 314 * which will be unwrapped before invoking the function. |
| 315 * @param {boolean} w3c Whether to return a W3C compliant element reference. |
212 * @param {boolean=} opt_unwrappedReturn Whether the function's return value | 316 * @param {boolean=} opt_unwrappedReturn Whether the function's return value |
213 * should be left unwrapped. | 317 * should be left unwrapped. |
214 * @return {*} An object containing a status and value property, where status | 318 * @return {*} An object containing a status and value property, where status |
215 * is a WebDriver status code and value is the wrapped value. If an | 319 * is a WebDriver status code and value is the wrapped value. If an |
216 * unwrapped return was specified, this will be the function's pure return | 320 * unwrapped return was specified, this will be the function's pure return |
217 * value. | 321 * value. |
218 */ | 322 */ |
219 function callFunction(shadowHostIds, func, args, opt_unwrappedReturn) { | 323 function callFunction(shadowHostIds, func, args, w3c, opt_unwrappedReturn) { |
220 var cache = getPageCache(); | 324 if (w3c) { |
| 325 w3cEnabled = true; |
| 326 ELEMENT_KEY = 'element-6066-11e4-a52e-4f735466cecf'; |
| 327 |
| 328 } |
| 329 var cache = getPageCache(null, w3cEnabled); |
221 cache.clearStale(); | 330 cache.clearStale(); |
222 if (shadowHostIds && SHADOW_DOM_ENABLED) { | 331 if (shadowHostIds && SHADOW_DOM_ENABLED) { |
223 for (var i = 0; i < shadowHostIds.length; i++) { | 332 for (var i = 0; i < shadowHostIds.length; i++) { |
224 var host = cache.retrieveItem(shadowHostIds[i]); | 333 var host = cache.retrieveItem(shadowHostIds[i]); |
225 // TODO(zachconrad): Use the olderShadowRoot API when available to check | 334 // TODO(zachconrad): Use the olderShadowRoot API when available to check |
226 // all of the shadow roots. | 335 // all of the shadow roots. |
227 cache = getPageCache(host.webkitShadowRoot); | 336 cache = getPageCache(host.webkitShadowRoot, w3cEnabled); |
228 cache.clearStale(); | 337 cache.clearStale(); |
229 } | 338 } |
230 } | 339 } |
231 | 340 |
232 if (opt_unwrappedReturn) | 341 if (opt_unwrappedReturn) |
233 return func.apply(null, unwrap(args, cache)); | 342 return func.apply(null, unwrap(args, cache)); |
234 | 343 |
235 var status = 0; | 344 var status = 0; |
236 try { | 345 try { |
237 var returnValue = wrap(func.apply(null, unwrap(args, cache))); | 346 var returnValue = wrap(func.apply(null, unwrap(args, cache))); |
238 } catch (error) { | 347 } catch (error) { |
239 status = error.code || StatusCode.UNKNOWN_ERROR; | 348 status = error.code || StatusCode.UNKNOWN_ERROR; |
240 var returnValue = error.message; | 349 var returnValue = error.message; |
241 } | 350 } |
242 return { | 351 return { |
243 status: status, | 352 status: status, |
244 value: returnValue | 353 value: returnValue |
245 } | 354 } |
246 } | 355 } |
OLD | NEW |