1 | // Copyright 2011 Software Freedom Conservancy. All Rights Reserved. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | goog.provide('webdriver.FirefoxDomExecutor'); |
16 | |
17 | goog.require('bot.response'); |
18 | goog.require('goog.json'); |
19 | goog.require('goog.userAgent.product'); |
20 | goog.require('webdriver.Command'); |
21 | goog.require('webdriver.CommandName'); |
22 | |
23 | |
24 | |
25 | /** |
26 | * @constructor |
27 | * @implements {webdriver.CommandExecutor} |
28 | */ |
29 | webdriver.FirefoxDomExecutor = function() { |
30 | if (!webdriver.FirefoxDomExecutor.isAvailable()) { |
31 | throw Error( |
32 | 'The current environment does not support the FirefoxDomExecutor'); |
33 | } |
34 | |
35 | /** @private {!Document} */ |
36 | this.doc_ = document; |
37 | |
38 | /** @private {!Element} */ |
39 | this.docElement_ = document.documentElement; |
40 | |
41 | this.docElement_.addEventListener( |
42 | webdriver.FirefoxDomExecutor.EventType_.RESPONSE, |
43 | goog.bind(this.onResponse_, this), false); |
44 | }; |
45 | |
46 | |
47 | /** |
48 | * @return {boolean} Whether the current environment supports the |
49 | * FirefoxDomExecutor. |
50 | */ |
51 | webdriver.FirefoxDomExecutor.isAvailable = function() { |
52 | return goog.userAgent.product.FIREFOX && |
53 | typeof document !== 'undefined' && |
54 | document.documentElement && |
55 | goog.isFunction(document.documentElement.hasAttribute) && |
56 | document.documentElement.hasAttribute('webdriver'); |
57 | }; |
58 | |
59 | |
60 | /** |
61 | * Attributes used to communicate with the FirefoxDriver extension. |
62 | * @enum {string} |
63 | * @private |
64 | */ |
65 | webdriver.FirefoxDomExecutor.Attribute_ = { |
66 | COMMAND: 'command', |
67 | RESPONSE: 'response' |
68 | }; |
69 | |
70 | |
71 | /** |
72 | * Events used to communicate with the FirefoxDriver extension. |
73 | * @enum {string} |
74 | * @private |
75 | */ |
76 | webdriver.FirefoxDomExecutor.EventType_ = { |
77 | COMMAND: 'webdriverCommand', |
78 | RESPONSE: 'webdriverResponse' |
79 | }; |
80 | |
81 | |
82 | /** |
83 | * The pending command, if any. |
84 | * @private {?{name:string, callback:!Function}} |
85 | */ |
86 | webdriver.FirefoxDomExecutor.prototype.pendingCommand_ = null; |
87 | |
88 | |
89 | /** @override */ |
90 | webdriver.FirefoxDomExecutor.prototype.execute = function(command, callback) { |
91 | if (this.pendingCommand_) { |
92 | throw Error('Currently awaiting a command response!'); |
93 | } |
94 | |
95 | this.pendingCommand_ = { |
96 | name: command.getName(), |
97 | callback: callback |
98 | }; |
99 | |
100 | var parameters = command.getParameters(); |
101 | |
102 | // There are two means for communicating with the FirefoxDriver: via |
103 | // HTTP using WebDriver's wire protocol and over the DOM using a custom |
104 | // JSON protocol. This class uses the latter. When the FirefoxDriver receives |
105 | // commands over HTTP, it builds a parameters object from the URL parameters. |
106 | // When an element ID is sent in the URL, it'll be decoded as just id:string |
107 | // instead of id:{ELEMENT:string}. When switching to a frame by element, |
108 | // however, the element ID is not sent through the URL, so we must make sure |
109 | // to encode that parameter properly here. It would be nice if we unified |
110 | // the two protocols used by the FirefoxDriver... |
111 | if (parameters['id'] && |
112 | parameters['id']['ELEMENT'] && |
113 | command.getName() != webdriver.CommandName.SWITCH_TO_FRAME) { |
114 | parameters['id'] = parameters['id']['ELEMENT']; |
115 | } |
116 | |
117 | var json = goog.json.serialize({ |
118 | 'name': command.getName(), |
119 | 'sessionId': { |
120 | 'value': parameters['sessionId'] |
121 | }, |
122 | 'parameters': parameters |
123 | }); |
124 | this.docElement_.setAttribute( |
125 | webdriver.FirefoxDomExecutor.Attribute_.COMMAND, json); |
126 | |
127 | var event = this.doc_.createEvent('Event'); |
128 | event.initEvent(webdriver.FirefoxDomExecutor.EventType_.COMMAND, |
129 | /*canBubble=*/true, /*cancelable=*/true); |
130 | |
131 | this.docElement_.dispatchEvent(event); |
132 | }; |
133 | |
134 | |
135 | /** @private */ |
136 | webdriver.FirefoxDomExecutor.prototype.onResponse_ = function() { |
137 | if (!this.pendingCommand_) { |
138 | return; // Not expecting a response. |
139 | } |
140 | |
141 | var command = this.pendingCommand_; |
142 | this.pendingCommand_ = null; |
143 | |
144 | var json = this.docElement_.getAttribute( |
145 | webdriver.FirefoxDomExecutor.Attribute_.RESPONSE); |
146 | if (!json) { |
147 | command.callback(Error('Empty command response!')); |
148 | return; |
149 | } |
150 | |
151 | this.docElement_.removeAttribute( |
152 | webdriver.FirefoxDomExecutor.Attribute_.COMMAND); |
153 | this.docElement_.removeAttribute( |
154 | webdriver.FirefoxDomExecutor.Attribute_.RESPONSE); |
155 | |
156 | try { |
157 | var response = bot.response.checkResponse( |
158 | /** @type {!bot.response.ResponseObject} */ (goog.json.parse(json))); |
159 | } catch (ex) { |
160 | command.callback(ex); |
161 | return; |
162 | } |
163 | |
164 | // Prior to Selenium 2.35.0, two commands are required to fully create a |
165 | // session: one to allocate the session, and another to fetch the |
166 | // capabilities. |
167 | if (command.name == webdriver.CommandName.NEW_SESSION && |
168 | goog.isString(response['value'])) { |
169 | var cmd = new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION). |
170 | setParameter('sessionId', response['value']); |
171 | this.execute(cmd, command.callback); |
172 | } else { |
173 | command.callback(null, response); |
174 | } |
175 | }; |