# lib/webdriver/firefoxdomexecutor.js

    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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415goog.provide('webdriver.FirefoxDomExecutor');1617goog.require('bot.response');18goog.require('goog.json');19goog.require('goog.userAgent.product');20goog.require('webdriver.Command');21goog.require('webdriver.CommandName');22232425/**26 * @constructor27 * @implements {webdriver.CommandExecutor}28 */29webdriver.FirefoxDomExecutor = function() {30  if (!webdriver.FirefoxDomExecutor.isAvailable()) {31    throw Error(32        'The current environment does not support the FirefoxDomExecutor');33  }3435  /** @private {!Document} */36  this.doc_ = document;3738  /** @private {!Element} */39  this.docElement_ = document.documentElement;4041  this.docElement_.addEventListener(42      webdriver.FirefoxDomExecutor.EventType_.RESPONSE,43      goog.bind(this.onResponse_, this), false);44};454647/**48 * @return {boolean} Whether the current environment supports the49 *     FirefoxDomExecutor.50 */51webdriver.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};585960/**61 * Attributes used to communicate with the FirefoxDriver extension.62 * @enum {string}63 * @private64 */65webdriver.FirefoxDomExecutor.Attribute_ = {66  COMMAND: 'command',67  RESPONSE: 'response'68};697071/**72 * Events used to communicate with the FirefoxDriver extension.73 * @enum {string}74 * @private75 */76webdriver.FirefoxDomExecutor.EventType_ = {77  COMMAND: 'webdriverCommand',78  RESPONSE: 'webdriverResponse'79};808182/**83 * The pending command, if any.84 * @private {?{name:string, callback:!Function}}85 */86webdriver.FirefoxDomExecutor.prototype.pendingCommand_ = null;878889/** @override */90webdriver.FirefoxDomExecutor.prototype.execute = function(command, callback) {91  if (this.pendingCommand_) {92    throw Error('Currently awaiting a command response!');93  }9495  this.pendingCommand_ = {96    name: command.getName(),97    callback: callback98  };99100  var parameters = command.getParameters();101102  // There are two means for communicating with the FirefoxDriver: via103  // HTTP using WebDriver's wire protocol and over the DOM using a custom104  // JSON protocol. This class uses the latter. When the FirefoxDriver receives105  // 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:string107  // 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 sure109  // to encode that parameter properly here. It would be nice if we unified110  // 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  var json = goog.json.serialize({117    'name': command.getName(),118    'sessionId': parameters['sessionId'],119    'parameters': parameters120  });121  this.docElement_.setAttribute(122      webdriver.FirefoxDomExecutor.Attribute_.COMMAND, json);123124  var event = this.doc_.createEvent('Event');125  event.initEvent(webdriver.FirefoxDomExecutor.EventType_.COMMAND,126      /*canBubble=*/true, /*cancelable=*/true);127128  this.docElement_.dispatchEvent(event);129};130131132/** @private */133webdriver.FirefoxDomExecutor.prototype.onResponse_ = function() {134  if (!this.pendingCommand_) {135    return;  // Not expecting a response.136  }137138  var command = this.pendingCommand_;139  this.pendingCommand_ = null;140141  var json = this.docElement_.getAttribute(142      webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);143  if (!json) {144    command.callback(Error('Empty command response!'));145    return;146  }147148  this.docElement_.removeAttribute(149      webdriver.FirefoxDomExecutor.Attribute_.COMMAND);150  this.docElement_.removeAttribute(151      webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);152153  try {154    var response = bot.response.checkResponse(155        /** @type {!bot.response.ResponseObject} */ (goog.json.parse(json)));156  } catch (ex) {157    command.callback(ex);158    return;159  }160161  // Prior to Selenium 2.35.0, two commands are required to fully create a162  // session: one to allocate the session, and another to fetch the163  // capabilities.164  if (command.name == webdriver.CommandName.NEW_SESSION &&165      goog.isString(response['value'])) {166    var cmd = new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).167        setParameter('sessionId', response['value']);168    this.execute(cmd, command.callback);169  } else {170    command.callback(null, response);171  }172};