Unregister from GCM when the only GCM app is removed
[chromium-blink-merge.git] / chrome / test / chromedriver / js / call_function.js
blob66f3c5709688bd7036823111579292faafe81ada
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.
5 /**
6  * Enum for WebDriver status codes.
7  * @enum {number}
8  */
9 var StatusCode = {
10   STALE_ELEMENT_REFERENCE: 10,
11   UNKNOWN_ERROR: 13,
14 /**
15  * Enum for node types.
16  * @enum {number}
17  */
18 var NodeType = {
19   ELEMENT: 1,
20   DOCUMENT: 9,
23 /**
24  * Dictionary key to use for holding an element ID.
25  * @const
26  * @type {string}
27  */
28 var ELEMENT_KEY = 'ELEMENT';
30 /**
31  * True if shadow dom is enabled.
32  * @const
33  * @type {boolean}
34  */
35 var SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function';
37 /**
38  * A cache which maps IDs <-> cached objects for the purpose of identifying
39  * a script object remotely.
40  * @constructor
41  */
42 function Cache() {
43   this.cache_ = {};
44   this.nextId_ = 1;
45   this.idPrefix_ = Math.random().toString();
48 Cache.prototype = {
50   /**
51    * Stores a given item in the cache and returns a unique ID.
52    *
53    * @param {!Object} item The item to store in the cache.
54    * @return {number} The ID for the cached item.
55    */
56   storeItem: function(item) {
57     for (var i in this.cache_) {
58       if (item == this.cache_[i])
59         return i;
60     }
61     var id = this.idPrefix_  + '-' + this.nextId_;
62     this.cache_[id] = item;
63     this.nextId_++;
64     return id;
65   },
67   /**
68    * Retrieves the cached object for the given ID.
69    *
70    * @param {number} id The ID for the cached item to retrieve.
71    * @return {!Object} The retrieved item.
72    */
73   retrieveItem: function(id) {
74     var item = this.cache_[id];
75     if (item)
76       return item;
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';
80     throw error;
81   },
83   /**
84    * Clears stale items from the cache.
85    */
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];
91     }
92   },
94   /**
95     * @private
96     * @param {!Node} node The node to check.
97     * @return {boolean} If the nodes is reachable.
98     */
99   isNodeReachable_: function(node) {
100     var nodeRoot = getNodeRootThroughAnyShadows(node);
101     return (nodeRoot == document);
102   }
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.
110  */
111 function getNodeRoot(node) {
112   while (node.parentNode) {
113     node = node.parentNode;
114   }
115   return node;
119  * Returns the root element of the node, jumping up through shadow roots if
120  * any are found.
121  */
122 function getNodeRootThroughAnyShadows(node) {
123   var root = getNodeRoot(node);
124   while (SHADOW_DOM_ENABLED && root instanceof ShadowRoot) {
125     root = getNodeRoot(root.host);
126   }
127   return root;
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.
135  */
136 function getPageCache(opt_doc) {
137   var doc = opt_doc || document;
138   var key = '$cdc_asdjflasutopfhvcZLmcfl_';
139   if (!(key in doc))
140     doc[key] = new Cache();
141   return doc[key];
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.
150  */
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)) {
156       var wrapped = {};
157       var root = getNodeRootThroughAnyShadows(value);
158       wrapped[ELEMENT_KEY] = getPageCache(root).storeItem(value);
159       return wrapped;
160     }
162     var obj = (typeof(value.length) == 'number') ? [] : {};
163     for (var prop in value)
164       obj[prop] = wrap(value[prop]);
165     return obj;
166   }
167   return value;
171  * Unwraps the given value by converting from object IDs to the cached
172  * objects.
174  * @param {*} value The value to unwrap.
175  * @param {Cache} cache The cache to retrieve wrapped elements from.
176  * @return {*} The unwrapped value.
177  */
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);
186     return obj;
187   }
188   return value;
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
209  *     value.
210  */
211 function callFunction(shadowHostIds, func, args, opt_unwrappedReturn) {
212   var cache = getPageCache();
213   cache.clearStale();
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);
220       cache.clearStale();
221     }
222   }
224   if (opt_unwrappedReturn)
225     return func.apply(null, unwrap(args, cache));
227   var status = 0;
228   try {
229     var returnValue = wrap(func.apply(null, unwrap(args, cache)));
230   } catch (error) {
231     status = error.code || StatusCode.UNKNOWN_ERROR;
232     var returnValue = error.message;
233   }
234   return {
235       status: status,
236       value: returnValue
237   }