# 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  }116117  var json = goog.json.serialize({118    'name': command.getName(),119    'sessionId': {120      'value': parameters['sessionId']121    },122    'parameters': parameters123  });124  this.docElement_.setAttribute(125      webdriver.FirefoxDomExecutor.Attribute_.COMMAND, json);126127  var event = this.doc_.createEvent('Event');128  event.initEvent(webdriver.FirefoxDomExecutor.EventType_.COMMAND,129      /*canBubble=*/true, /*cancelable=*/true);130131  this.docElement_.dispatchEvent(event);132};133134135/** @private */136webdriver.FirefoxDomExecutor.prototype.onResponse_ = function() {137  if (!this.pendingCommand_) {138    return;  // Not expecting a response.139  }140141  var command = this.pendingCommand_;142  this.pendingCommand_ = null;143144  var json = this.docElement_.getAttribute(145      webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);146  if (!json) {147    command.callback(Error('Empty command response!'));148    return;149  }150151  this.docElement_.removeAttribute(152      webdriver.FirefoxDomExecutor.Attribute_.COMMAND);153  this.docElement_.removeAttribute(154      webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);155156  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  }163164  // Prior to Selenium 2.35.0, two commands are required to fully create a165  // session: one to allocate the session, and another to fetch the166  // 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};