| 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 | }; |