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 17 matching lines...) Expand all Loading... |
28 var ELEMENT_KEY = 'ELEMENT'; | 28 var ELEMENT_KEY = 'ELEMENT'; |
29 | 29 |
30 /** | 30 /** |
31 * A cache which maps IDs <-> cached objects for the purpose of identifying | 31 * A cache which maps IDs <-> cached objects for the purpose of identifying |
32 * a script object remotely. | 32 * a script object remotely. |
33 * @constructor | 33 * @constructor |
34 */ | 34 */ |
35 function Cache() { | 35 function Cache() { |
36 this.cache_ = {}; | 36 this.cache_ = {}; |
37 this.nextId_ = 1; | 37 this.nextId_ = 1; |
| 38 this.idPrefix_ = Math.random().toString(); |
38 } | 39 } |
39 | 40 |
40 Cache.prototype = { | 41 Cache.prototype = { |
41 | 42 |
42 /** | 43 /** |
43 * Stores a given item in the cache and returns a unique ID. | 44 * Stores a given item in the cache and returns a unique ID. |
44 * | 45 * |
45 * @param {!Object} item The item to store in the cache. | 46 * @param {!Object} item The item to store in the cache. |
46 * @return {number} The ID for the cached item. | 47 * @return {number} The ID for the cached item. |
47 */ | 48 */ |
48 storeItem_: function(item) { | 49 storeItem: function(item) { |
49 for (var i in this.cache_) { | 50 for (var i in this.cache_) { |
50 if (item == this.cache_[i]) | 51 if (item == this.cache_[i]) |
51 return i; | 52 return i; |
52 } | 53 } |
53 var id = this.nextId_.toString(); | 54 var id = this.idPrefix_ + ':' + this.nextId_; |
54 this.cache_[id] = item; | 55 this.cache_[id] = item; |
55 this.nextId_++; | 56 this.nextId_++; |
56 return id; | 57 return id; |
57 }, | 58 }, |
58 | 59 |
59 /** | 60 /** |
60 * Retrieves the cached object for the given ID. | 61 * Retrieves the cached object for the given ID. |
61 * | 62 * |
62 * @param {number} id The ID for the cached item to retrieve. | 63 * @param {number} id The ID for the cached item to retrieve. |
63 * @return {!Object} The retrieved item. | 64 * @return {!Object} The retrieved item. |
64 */ | 65 */ |
65 retrieveItem_: function(id) { | 66 retrieveItem: function(id) { |
66 var item = this.cache_[id]; | 67 var item = this.cache_[id]; |
67 if (item) | 68 if (item) |
68 return item; | 69 return item; |
69 var error = new Error('not in cache'); | 70 var error = new Error('not in cache'); |
70 error.code = StatusCode.STALE_ELEMENT_REFERENCE; | 71 error.code = StatusCode.STALE_ELEMENT_REFERENCE; |
71 error.message = 'element is not attached to the page document'; | 72 error.message = 'element is not attached to the page document'; |
72 throw error; | 73 throw error; |
73 }, | 74 }, |
74 | 75 |
75 /** | 76 /** |
76 * Clears stale items from the cache. | 77 * Clears stale items from the cache. |
77 */ | 78 */ |
78 clearStale: function() { | 79 clearStale: function() { |
79 for (var id in this.cache_) { | 80 for (var id in this.cache_) { |
80 var node = this.cache_[id]; | 81 var node = this.cache_[id]; |
81 while (node) { | 82 while (node) { |
82 if (node == document) | 83 if (node == document) |
83 break; | 84 break; |
84 node = node.parentNode; | 85 node = node.parentNode; |
85 } | 86 } |
86 if (!node) | 87 if (!node) |
87 delete this.cache_[id]; | 88 delete this.cache_[id]; |
88 } | 89 } |
89 }, | |
90 | |
91 /** | |
92 * Wraps the given value to be transmitted remotely by converting | |
93 * appropriate objects to cached object IDs. | |
94 * | |
95 * @param {*} value The value to wrap. | |
96 * @return {*} The wrapped value. | |
97 */ | |
98 wrap: function(value) { | |
99 if (typeof(value) == 'object' && value != null) { | |
100 var nodeType = value['nodeType']; | |
101 if (nodeType == NodeType.ELEMENT || nodeType == NodeType.DOCUMENT) { | |
102 var wrapped = {}; | |
103 wrapped[ELEMENT_KEY] = this.storeItem_(value); | |
104 return wrapped; | |
105 } | |
106 | |
107 var obj = (typeof(value.length) == 'number') ? [] : {}; | |
108 for (var prop in value) | |
109 obj[prop] = this.wrap(value[prop]); | |
110 return obj; | |
111 } | |
112 return value; | |
113 }, | |
114 | |
115 /** | |
116 * Unwraps the given value by converting from object IDs to the cached | |
117 * objects. | |
118 * | |
119 * @param {*} value The value to unwrap. | |
120 * @return {*} The unwrapped value. | |
121 */ | |
122 unwrap: function(value) { | |
123 if (typeof(value) == 'object' && value != null) { | |
124 if (ELEMENT_KEY in value) | |
125 return this.retrieveItem_(value[ELEMENT_KEY]); | |
126 | |
127 var obj = (typeof(value.length) == 'number') ? [] : {}; | |
128 for (var prop in value) | |
129 obj[prop] = this.unwrap(value[prop]); | |
130 return obj; | |
131 } | |
132 return value; | |
133 } | 90 } |
134 }; | 91 }; |
135 | 92 |
136 /** | 93 /** |
137 * Returns the global object cache for the page. | 94 * Returns the global object cache for the page. |
| 95 * @param {Document=} opt_doc The document whose cache to retrieve. Defaults to |
| 96 * the current document. |
138 * @return {!Cache} The page's object cache. | 97 * @return {!Cache} The page's object cache. |
139 */ | 98 */ |
140 function getPageCache() { | 99 function getPageCache(opt_doc) { |
| 100 var doc = opt_doc || document; |
141 // We use the same key as selenium's javascript/atoms/inject.js. | 101 // We use the same key as selenium's javascript/atoms/inject.js. |
142 var key = '$wdc_'; | 102 var key = '$wdc_'; |
143 if (!(key in document)) | 103 if (!(key in doc)) |
144 document[key] = new Cache(); | 104 doc[key] = new Cache(); |
145 return document[key]; | 105 return doc[key]; |
146 } | 106 } |
147 | 107 |
148 /** | 108 /** |
| 109 * Wraps the given value to be transmitted remotely by converting |
| 110 * appropriate objects to cached object IDs. |
| 111 * |
| 112 * @param {*} value The value to wrap. |
| 113 * @return {*} The wrapped value. |
| 114 */ |
| 115 function wrap(value) { |
| 116 if (typeof(value) == 'object' && value != null) { |
| 117 var nodeType = value['nodeType']; |
| 118 if (nodeType == NodeType.ELEMENT || nodeType == NodeType.DOCUMENT) { |
| 119 var wrapped = {}; |
| 120 wrapped[ELEMENT_KEY] = getPageCache(value.ownerDocument).storeItem(value); |
| 121 return wrapped; |
| 122 } |
| 123 |
| 124 var obj = (typeof(value.length) == 'number') ? [] : {}; |
| 125 for (var prop in value) |
| 126 obj[prop] = wrap(value[prop]); |
| 127 return obj; |
| 128 } |
| 129 return value; |
| 130 } |
| 131 |
| 132 /** |
| 133 * Unwraps the given value by converting from object IDs to the cached |
| 134 * objects. |
| 135 * |
| 136 * @param {*} value The value to unwrap. |
| 137 * @param {Cache} cache The cache to retrieve wrapped elements from. |
| 138 * @return {*} The unwrapped value. |
| 139 */ |
| 140 function unwrap(value, cache) { |
| 141 if (typeof(value) == 'object' && value != null) { |
| 142 if (ELEMENT_KEY in value) |
| 143 return cache.retrieveItem(value[ELEMENT_KEY]); |
| 144 |
| 145 var obj = (typeof(value.length) == 'number') ? [] : {}; |
| 146 for (var prop in value) |
| 147 obj[prop] = unwrap(value[prop], cache); |
| 148 return obj; |
| 149 } |
| 150 return value; |
| 151 } |
| 152 |
| 153 /** |
149 * Calls a given function and returns its value. | 154 * Calls a given function and returns its value. |
150 * | 155 * |
151 * The inputs to and outputs of the function will be unwrapped and wrapped | 156 * The inputs to and outputs of the function will be unwrapped and wrapped |
152 * respectively, unless otherwise specified. This wrapping involves converting | 157 * respectively, unless otherwise specified. This wrapping involves converting |
153 * between cached object reference IDs and actual JS objects. The cache will | 158 * between cached object reference IDs and actual JS objects. The cache will |
154 * automatically be pruned each call to remove stale references. | 159 * automatically be pruned each call to remove stale references. |
155 * | 160 * |
156 * @param {function(...[*]) : *} func The function to invoke. | 161 * @param {function(...[*]) : *} func The function to invoke. |
157 * @param {!Array.<*>} args The array of arguments to supply to the function, | 162 * @param {!Array.<*>} args The array of arguments to supply to the function, |
158 * which will be unwrapped before invoking the function. | 163 * which will be unwrapped before invoking the function. |
159 * @param {boolean=} opt_unwrappedReturn Whether the function's return value | 164 * @param {boolean=} opt_unwrappedReturn Whether the function's return value |
160 * should be left unwrapped. | 165 * should be left unwrapped. |
161 * @return {*} An object containing a status and value property, where status | 166 * @return {*} An object containing a status and value property, where status |
162 * is a WebDriver status code and value is the wrapped value. If an | 167 * is a WebDriver status code and value is the wrapped value. If an |
163 * unwrapped return was specified, this will be the function's pure return | 168 * unwrapped return was specified, this will be the function's pure return |
164 * value. | 169 * value. |
165 */ | 170 */ |
166 function callFunction(func, args, opt_unwrappedReturn) { | 171 function callFunction(func, args, opt_unwrappedReturn) { |
167 var cache = getPageCache(); | 172 var cache = getPageCache(); |
168 cache.clearStale(); | 173 cache.clearStale(); |
169 | 174 |
170 if (opt_unwrappedReturn) | 175 if (opt_unwrappedReturn) |
171 return func.apply(null, cache.unwrap(args)); | 176 return func.apply(null, unwrap(args, cache)); |
172 | 177 |
173 var status = 0; | 178 var status = 0; |
174 try { | 179 try { |
175 var returnValue = cache.wrap(func.apply(null, cache.unwrap(args))); | 180 var returnValue = wrap(func.apply(null, unwrap(args, cache))); |
176 } catch (error) { | 181 } catch (error) { |
177 status = error.code || StatusCode.UNKNOWN_ERROR; | 182 status = error.code || StatusCode.UNKNOWN_ERROR; |
178 var returnValue = error.message; | 183 var returnValue = error.message; |
179 } | 184 } |
180 return { | 185 return { |
181 status: status, | 186 status: status, |
182 value: returnValue | 187 value: returnValue |
183 } | 188 } |
184 } | 189 } |
OLD | NEW |